I need to include the PID in my log4js logs. I see many examples which use the thread context. However, these need to be set on each individual thread created. I am constrained against doing this.
I need a solution that, either, does not use the thread context, or, can set the PID on all thread contexts, for any thread that may be created, from any arbitrary class.
Please create a feature request on the Log4j2 issue tracker to make this a built-in feature.
For now, you can create a custom plugin. See code below. This will allow you to specify %pid in the pattern layout (similar to %m for the message).
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
#Plugin(name = "ProcessIdPatternConverter", category = "Converter")
#ConverterKeys({ "pid", "processId" })
public final class ProcessIdPatternConverter extends LogEventPatternConverter {
private final String pid;
private ProcessIdPatternConverter(String[] options) {
super("Process ID", "pid");
String temp = options.length > 0 ? options[0] : "???";
try {
// likely works on most platforms
temp = ManagementFactory.getRuntimeMXBean().getName().split("#")[0];
} catch (final Exception ex) {
try {
// try a Linux-specific way
temp = new File("/proc/self").getCanonicalFile().getName();
} catch (final IOException ignoredUseDefault) {}
}
pid = temp;
}
/**
* Obtains an instance of ProcessIdPatternConverter.
*
* #param options users may specify a default like {#code %pid{NOPID} }
* #return instance of ProcessIdPatternConverter.
*/
public static ProcessIdPatternConverter newInstance(final String[] options) {
return new ProcessIdPatternConverter(options);
}
#Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
toAppendTo.append(pid);
}
}
See the manual for more details on how Log4j2 plugins work.
One way to let Log4j2 recognize your plugin is by specifying the package name of the plugin class in the packages attribute of the configuration:
<Configuration status="trace"
packages="com.myorg.mypluginpackage">
(Trace switches on Log4j2 internal debugging to help with troubleshooting.)
Related
Like we do the following in Spring
#Value("${varName:0}")
int varName;
Is there a way to do this using Google Guice?
In Guice you would annotate the method and make it optional. You then just assign the default value. If no property is there to be injected, it will be the default value.
For example:
public class TestModule3 extends AbstractModule {
#Override
protected void configure() {
// Properties p = new Properties();
// p.setProperty("myValue", "12");
// Names.bindProperties(binder(), p); // this binds the properties that usually come for a file
bind(Manager.class).to(ManagerImpl.class).in(Singleton.class);
}
public static interface Manager {
public void talk();
}
public static class ManagerImpl implements Manager {
#Inject(optional = true)
#Named("myValue")
int test = 0;
#Override
public void talk() {
System.out.println(test);
}
}
public static void main(String[] args) {
Manager instance = Guice.createInjector(new TestModule3()).getInstance(Manager.class);
instance.talk();
}
}
This will print "0" for you, because I commented out the property binding. If you remove the comments, it will bind the value 12 to the String myValue. The inject annotation takes care of the rest.
Hope that helps,
EDIT:
As #TavianBarnes pointed out, Guice 4+ has an OptionalBinder. I tried this for your usecase and could not make it work out of the box.
It appears that OptionalBinding is very useful for classes (actual instances), not for properties. Here is why:
You have to know all the properties in advance and bind them to their defaults. It is easy to forget them. The example shown by OP also shows that he does not know if he has the property available (based on the name).
Default implementation of property bindings don't work in combo with the OptionalBinding.
So the way you can make that work is like this:
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, Names.named("myValue"))).setDefault()
.toInstance("777");
Properties p = new Properties();
p.setProperty("myValue", "12");
// use enumeration to include the default properties
for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
String propertyName = (String) e.nextElement();
String value = p.getProperty(propertyName);
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, Names.named(propertyName))).setBinding()
.toInstance(value);
}
I had to copy the Named binding code and change it to support optional bindings.
In summary:
I would prefer to use the optional=true flag + default value in code for properties.
Use the OptionalBinding for actual classes that can be optional.
Finally, there is one more thing you could do - this is my solution in my code. I have a similar requirement (not the optional, but default values).
I want:
Bind my properties
Check if my properties are a variable
Replace the variable
If the variable is not available set a default
Apache offers a handy library for this already which I reuse. This is how my properties look like:
myProperty=${ENV_VAR_NAME:-600}
This is the default annotation of how to define a default value.
The above property says:
Use the evnironment variable "ENV_VAR_NAME".
If "ENV_VAR_NAME" is not set, use the value "600"
Then I bind it as follows:
InputStream resourceAsStream = getClass().getResourceAsStream(path);
if(resourceAsStream == null) {
throw new IllegalArgumentException("No property file found for path: " + path);
}
try {
p.load(resourceAsStream);
EnvironmentVariableSubstitutor envSubstitutor = new EnvironmentVariableSubstitutor(false);
Set<Object> keys = p.keySet();
for(Object k : keys) {
String property = p.getProperty(k.toString());
property = envSubstitutor.replace(property);
p.put(k, property);
}
} catch (IOException e) {
throw new IllegalStateException("Could not load properties", e);
} finally {
try {
resourceAsStream.close();
} catch (IOException e) {
log.error("Could not close stream for resource " + path);
}
}
Names.bindProperties(binder(), p);
What this code does is:
Load the properties from a resource file
Use the EnvironmentVariableSubstitutor to process the values of the properties and overwrite the result. (see loop)
finally, bind the modified properties to their names.
These are all the solutions I can come up with at short notice :) let me know if something's unclear
Edit 2:
there is some info on OptionalBindings and properties + how to handle default values in this google thread as well: https://groups.google.com/forum/#!topic/google-guice/7Ga79iU_sb0
Artur
I am using akka actor system for multi threading. It is working fine in normal use-cases. However, Akka is closing JVM on fatal error. Please let me know how I can configure Akka to disable "akka.jvm-exit-on-fatal-error" in java. Below is code.
public class QueueListener implements MessageListener {
private String _queueName=null;
public static boolean isActorinit=false;
public static ActorSystem system=null;
private ActorRef myActor;
public QueueListener(String actorId, String qName){
this._queueName = qName;
if(!isActorinit){
system=ActorSystem.create(actorId);
isActorinit=true;
}
myActor=system.actorOf( Props.create(MessageExecutor.class, qName),qName+"id");
}
/*
* (non-Javadoc)
* #see javax.jms.MessageListener#onMessage(javax.jms.Message)
*/
#Override
public void onMessage(Message msg) {
executeRequest(msg);
}
/** This method will process the message fetch by the listener.
*
* #param msg - javax.jms.Messages parameter get queue message
*/
private void executeRequest(Message msg){
String requestData=null;
try {
if(msg instanceof TextMessage){
TextMessage textMessage= (TextMessage) msg;
requestData = textMessage.getText().toString();
}else if(msg instanceof ObjectMessage){
ObjectMessage objMsg = (ObjectMessage) msg;
requestData = objMsg.getObject().toString();
}
myActor.tell(requestData, ActorRef.noSender());
} catch (JMSException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
Create an application.conf file in your project (sr/main/resources for example) and add the following content:
akka {
jvm-exit-on-fatal-error = false
}
No need to create new config file if you already have one of course, in that case it is just adding the new entry:
jvm-exit-on-fatal-error = false
Be careful. Letting the JVM run after fatal errors like OutOfMemory is normally not a good idea and leads to serious problems.
See here for the configuration details - you can provide a separate config file, but for the small number of changes I was making to the akka config (and also given that I was already using several Spring config files) I found it easier to construct and load the configuration programmatically. Your config would look something like:
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
StringBuilder configBuilder = new StringBuilder();
configBuilder.append("{\"akka\" : { \"jvm-exit-on-fatal-error\" : \"off\"}}");
Config mergedConfig = ConfigFactory.load(ConfigFactory.parseString(configBuilder.toString()).withFallback(ConfigFactory.load()));
system = ActorSystem.create(actorId, mergedConfig);
This is loading the default Config, overriding its jvm-exit-on-fatal-error entry, and using this new Config as the config for the ActorSystem. I haven't tested this particular config, so there is a 50% chance that you'll get some sort of JSON parsing error when you try to use it; for comparison, the actual config I use which DOES parse correctly (but which doesn't override jvm-exit-on-fatal-error) is
private ActorSystem createActorSystem(int batchManagerCount) {
int maxActorCount = batchManagerCount * 5 + 1;
StringBuilder configBuilder = new StringBuilder();
configBuilder.append("{\"akka\" : { \"actor\" : { \"default-dispatcher\" : {");
configBuilder.append("\"type\" : \"Dispatcher\",");
configBuilder.append("\"executor\" : \"default-executor\",");
configBuilder.append("\"throughput\" : \"1\",");
configBuilder.append("\"default-executor\" : { \"fallback\" : \"thread-pool-executor\" },");
StringBuilder executorConfigBuilder = new StringBuilder();
executorConfigBuilder.append("\"thread-pool-executor\" : {");
executorConfigBuilder.append("\"keep-alive-time\" : \"60s\",");
executorConfigBuilder.append(String.format("\"core-pool-size-min\" : \"%d\",", maxActorCount));
executorConfigBuilder.append(String.format("\"core-pool-size-max\" : \"%d\",", maxActorCount));
executorConfigBuilder.append(String.format("\"max-pool-size-min\" : \"%d\",", maxActorCount));
executorConfigBuilder.append(String.format("\"max-pool-size-max\" : \"%d\",", maxActorCount));
executorConfigBuilder.append("\"task-queue-size\" : \"-1\",");
executorConfigBuilder.append("\"task-queue-type\" : \"linked\",");
executorConfigBuilder.append("\"allow-core-timeout\" : \"on\"");
executorConfigBuilder.append("}");
configBuilder.append(executorConfigBuilder.toString());
configBuilder.append("}}}}");
Config mergedConfig = ConfigFactory.load(ConfigFactory.parseString(configBuilder.toString()).withFallback(ConfigFactory.load()));
return ActorSystem.create(String.format("PerformanceAsync%s", systemId), mergedConfig);
}
As you can see I was primarily interested in tweaking the dispatcher.
Is it possible to have my app update the config settings at runtime? I can easily expose the settings I want in my UI but is there a way to allow the user to update settings and make them permanent ie save them to the config.yaml file? The only way I can see it to update the file by hand then restart the server which seems a bit limiting.
Yes. It is possible to reload the service classes at runtime.
Dropwizard by itself does not have the way to reload the app, but jersey has.
Jersey uses a container object internally to maintain the running application. Dropwizard uses the ServletContainer class of Jersey to run the application.
How to reload the app without restarting it -
Get a handle to the container used internally by jersey
You can do this by registering a AbstractContainerLifeCycleListener in Dropwizard Environment before starting the app. and implement its onStartup method as below -
In your main method where you start the app -
//getting the container instance
environment.jersey().register(new AbstractContainerLifecycleListener() {
#Override
public void onStartup(Container container) {
//initializing container - which will be used to reload the app
_container = container;
}
});
Add a method to your app to reload the app. It will take in the list of string which are the names of the service classes you want to reload. This method will call the reload method of the container with the new custom DropWizardConfiguration instance.
In your Application class
public static synchronized void reloadApp(List<String> reloadClasses) {
DropwizardResourceConfig dropwizardResourceConfig = new DropwizardResourceConfig();
for (String className : reloadClasses) {
try {
Class<?> serviceClass = Class.forName(className);
dropwizardResourceConfig.registerClasses(serviceClass);
System.out.printf(" + loaded class %s.\n", className);
} catch (ClassNotFoundException ex) {
System.out.printf(" ! class %s not found.\n", className);
}
}
_container.reload(dropwizardResourceConfig);
}
For more details see the example documentation of jersey - jersey example for reload
Consider going through the code and documentation of following files in Dropwizard/Jersey for a better understanding -
Container.java
ContainerLifeCycleListener.java
ServletContainer.java
AbstractContainerLifeCycleListener.java
DropWizardResourceConfig.java
ResourceConfig.java
No.
Yaml file is parsed at startup and given to the application as Configuration object once and for all. I believe you can change the file after that but it wouldn't affect your application until you restart it.
Possible follow up question: Can one restart the service programmatically?
AFAIK, no. I've researched and read the code somewhat for that but couldn't find a way to do that yet. If there is, I'd love to hear that :).
I made a task that reloads the main yaml file (it would be useful if something in the file changes). However, it is not reloading the environment. After researching this, Dropwizard uses a lot of final variables and it's quite hard to reload these on the go, without restarting the app.
class ReloadYAMLTask extends Task {
private String yamlFileName;
ReloadYAMLTask(String yamlFileName) {
super("reloadYaml");
this.yamlFileName = yamlFileName;
}
#Override
public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
if (yamlFileName != null) {
ConfigurationFactoryFactory configurationFactoryFactory = new DefaultConfigurationFactoryFactory<ReportingServiceConfiguration>();
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = Jackson.newObjectMapper();
final ConfigurationFactory<ServiceConfiguration> configurationFactory = configurationFactoryFactory.create(ServiceConfiguration.class, validator, objectMapper, "dw");
File confFile = new File(yamlFileName);
configurationFactory.build(new File(confFile.toURI()));
}
}
}
You can change the configuration in the YAML and read it while your application is running. This will not however restart the server or change any server configurations. You will be able to read any changed custom configurations and use them. For example, you can change the logging level at runtime or reload other custom settings.
My solution -
Define a custom server command. You should use this command to start your application instead of the "server" command.
ArgsServerCommand.java
public class ArgsServerCommand<WC extends WebConfiguration> extends EnvironmentCommand<WC> {
private static final Logger LOGGER = LoggerFactory.getLogger(ArgsServerCommand.class);
private final Class<WC> configurationClass;
private Namespace _namespace;
public static String COMMAND_NAME = "args-server";
public ArgsServerCommand(Application<WC> application) {
super(application, "args-server", "Runs the Dropwizard application as an HTTP server specific to my settings");
this.configurationClass = application.getConfigurationClass();
}
/*
* Since we don't subclass ServerCommand, we need a concrete reference to the configuration
* class.
*/
#Override
protected Class<WC> getConfigurationClass() {
return configurationClass;
}
public Namespace getNamespace() {
return _namespace;
}
#Override
protected void run(Environment environment, Namespace namespace, WC configuration) throws Exception {
_namespace = namespace;
final Server server = configuration.getServerFactory().build(environment);
try {
server.addLifeCycleListener(new LifeCycleListener());
cleanupAsynchronously();
server.start();
} catch (Exception e) {
LOGGER.error("Unable to start server, shutting down", e);
server.stop();
cleanup();
throw e;
}
}
private class LifeCycleListener extends AbstractLifeCycle.AbstractLifeCycleListener {
#Override
public void lifeCycleStopped(LifeCycle event) {
cleanup();
}
}
}
Method to reload in your Application -
_ymlFilePath = null; //class variable
public static boolean reloadConfiguration() throws IOException, ConfigurationException {
boolean reloaded = false;
if (_ymlFilePath == null) {
List<Command> commands = _configurationBootstrap.getCommands();
for (Command command : commands) {
String commandName = command.getName();
if (commandName.equals(ArgsServerCommand.COMMAND_NAME)) {
Namespace namespace = ((ArgsServerCommand) command).getNamespace();
if (namespace != null) {
_ymlFilePath = namespace.getString("file");
}
}
}
}
ConfigurationFactoryFactory configurationFactoryFactory = _configurationBootstrap.getConfigurationFactoryFactory();
ValidatorFactory validatorFactory = _configurationBootstrap.getValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = _configurationBootstrap.getObjectMapper();
ConfigurationSourceProvider provider = _configurationBootstrap.getConfigurationSourceProvider();
final ConfigurationFactory<CustomWebConfiguration> configurationFactory = configurationFactoryFactory.create(CustomWebConfiguration.class, validator, objectMapper, "dw");
if (_ymlFilePath != null) {
// Refresh logging level.
CustomWebConfiguration webConfiguration = configurationFactory.build(provider, _ymlFilePath);
LoggingFactory loggingFactory = webConfiguration.getLoggingFactory();
loggingFactory.configure(_configurationBootstrap.getMetricRegistry(), _configurationBootstrap.getApplication().getName());
// Get my defined custom settings
CustomSettings customSettings = webConfiguration.getCustomSettings();
reloaded = true;
}
return reloaded;
}
Although this feature isn't supported out of the box by dropwizard, you're able to accomplish this fairly easy with the tools they give you.
Before I get started, note that this isn't a complete solution for the question asked as it doesn't persist the updated config values to the config.yml. However, this would be easy enough to implement yourself simply by writing to the config file from the application. If anyone would like to write this implementation feel free to open a PR on the example project I've linked below.
Code
Start off with a minimal config:
config.yml
myConfigValue: "hello"
And it's corresponding configuration file:
ExampleConfiguration.java
public class ExampleConfiguration extends Configuration {
private String myConfigValue;
public String getMyConfigValue() {
return myConfigValue;
}
public void setMyConfigValue(String value) {
myConfigValue = value;
}
}
Then create a task which updates the config:
UpdateConfigTask.java
public class UpdateConfigTask extends Task {
ExampleConfiguration config;
public UpdateConfigTask(ExampleConfiguration config) {
super("updateconfig");
this.config = config;
}
#Override
public void execute(Map<String, List<String>> parameters, PrintWriter output) {
config.setMyConfigValue("goodbye");
}
}
Also for demonstration purposes, create a resource which allows you to get the config value:
ConfigResource.java
#Path("/config")
public class ConfigResource {
private final ExampleConfiguration config;
public ConfigResource(ExampleConfiguration config) {
this.config = config;
}
#GET
public Response handleGet() {
return Response.ok().entity(config.getMyConfigValue()).build();
}
}
Finally wire everything up in your application:
ExampleApplication.java (exerpt)
environment.jersey().register(new ConfigResource(configuration));
environment.admin().addTask(new UpdateConfigTask(configuration));
Usage
Start up the application then run:
$ curl 'http://localhost:8080/config'
hello
$ curl -X POST 'http://localhost:8081/tasks/updateconfig'
$ curl 'http://localhost:8080/config'
goodbye
How it works
This works simply by passing the same reference to the constructor of ConfigResource.java and UpdateConfigTask.java. If you aren't familiar with the concept see here:
Is Java "pass-by-reference" or "pass-by-value"?
The linked classes above are to a project I've created which demonstrates this as a complete solution. Here's a link to the project:
scottg489/dropwizard-runtime-config-example
Footnote: I haven't verified this works with the built in configuration. However, the dropwizard Configuration class which you need to extend for your own configuration does have various "setters" for internal configuration, but it may not be safe to update those outside of run().
Disclaimer: The project I've linked here was created by me.
I am developing an android application and there are several variables that i might need to change wihtout wanting to recompile and deploy the android application into the android smartphone.
In java i would do a propertyloader like the following i have done in java before:
public class PropertyLoader {
private static Properties props = null;
/**
* Method initializes the PropertyLoader by reading all configuration settings
* from the RememberMeServer.conf file.
*/
public static void initializeProperties() {
String propFile = getCatalinaDirectory() + "RememberMeServer.conf";
System.out.println(propFile);
File configFile = new File(propFile);
InputStream inputStream = null;
try {
inputStream = new FileInputStream(configFile);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
props = new Properties();
try {
props.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns a string value from the configuration file.
*
* #param key a string which represents the key of the requested value in the configuration file
* #return the value of the requested key from the property file as a string or null if the
* requested key could not be found.
*/
public static String getStringValue(String key) {
return props == null ? null : props.getProperty(key);
}
/**
* Returns an int value from the configuration file.
*
* #param key a string which represents the key of the requested value in the configuration file
* #return the value of the requested key from the property file as an int or null if the
* requested key could not be found.
*/
public static Integer getIntValue(String key) {
return props == null ? null : Integer.valueOf(props.getProperty(key));
}
/**
* Returns the directory of the project�s workspace as a string
*
* #return Returns the directory of the project�s workspace as a string
*/
public static String getWorkspaceDirectory() {
URL url = PropertyLoader.class.getClassLoader().getResource(
"hibernate.cfg.xml");
return url.getFile().substring(0,
url.getFile().lastIndexOf("hibernate.cfg.xml"));
}
/**
* Returns the directory of the servlet container catalina directory as a string
*
* #return Returns the directory of the servlet container catalina directory as a string
*/
public static String getCatalinaDirectory() {
String workspace = getWorkspaceDirectory();
return workspace
.substring(0, workspace.lastIndexOf("RememberMeServer"));
}
}
Although in android there is something called SharedPreferences which i already use in my application. Although i never use the SharedPreferences to change variable information directly in the file but only from the application's code.
What is the best alternative in an android application?
Because what i want to achieve is, to me, better represented by a property loader which saves things that i do not want to hard code in my java code.
You can use xml file to store your configuration and access the same way as key being the tag and value being the tag value.
For example-
Path = yourapp/res/xml/RememberMeServer.xml
RememberMeServer.xml contents -
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>day</key>
<integer>1</integer>
<key>hour</key>
<integer>12</integer>
<key>minute</key>
<integer>10</integer>
<key>second</key>
<integer>14</integer>
<key>background</key>
<string>Graphic_6_7_Red</string>
<key>Online</key>
<true/>
</dict>
</plist>
Then to access and use the key-value -
Class Code -
String day, hour, minute, second, background;
boolean status;
int resId = getResources().getIdentifier("xml/" + RememberMeServer,
"string", getActivity().getPackageName());
XmlPullParser xpp0 = getResources().getXml(resId);
XMLData xml = new XMLData(xpp0);
day = (xml.getValue("day"));
hour= (xml.getValue("hour"));
second= (xml.getValue("second"));
minute= (xml.getValue("minute"));
background= (xml.getValue("Graphic_6_7_Red"));
status= (xml.checkFieldPresence("Online"));
Class XMLData.java -
(This class contains the logic of accessing value by key)
public String getValue(String key) {
int start = this.xmldoc.indexOf("<key>"+ key + "</key>");
if(start == -1)
return "";
String xmldoc2 = this.xmldoc.substring(start);
start = xmldoc2.indexOf("</key>");
xmldoc2 = xmldoc2.substring(start);
xmldoc2 = xmldoc2.substring(6);
start = xmldoc2.indexOf(">");
xmldoc2 = xmldoc2.substring(start + 1);
int end = xmldoc2.indexOf("</");
xmldoc2 = xmldoc2.substring(0, end);
return xmldoc2;
}
public boolean checkFieldPresence(String key)
{
int start = this.xmldoc.indexOf(key + "</key>");
if(start == -1)
return false;
else
return true;
}
NOTE:
You can change any value for any key in your file RememberMeServer.xml.
This provides the flexibility that you don't have to worry about getting the saved value of the key. Whatever will be the value, the methods return those values by their keys.
SharedPreferences is also a good thing but as you told that you have lots of variables that change a lot so the best solution is to put them all in a xml file and access them when needed. You can change any value according to the requirement and still be content specific. The whole logic is centered within a single xml file and you can look and change just a single file to modify any value.
Option 1: App Resources
What I really don't understand is why you are not using the normal app resources? From what you describe that would be exactly what you are looking for. You can specify all kinds of values in there and things like ip and port and other important values should always be in the resources anyway. If you for example need an integer somewhere you can just define an integer in res/values/ints.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="some_integer">27</integer>
<integer name="another_integer">42</integer>
</resources>
Or a string can be defined in res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="some_string">Some Text</integer>
<string name="another_string">qwerty</integer>
</resources>
And all values you define in those resources can be loaded later on at any time:
Resources resources = getResources();
int someInteger = resources.getInteger(R.integer.some_integer);
String someString = resources.getString(R.string.some_string);
You can find more information about the app resources in the official documentation.
Option 2: SharedPreferences
Another option are of course the SharedPreferences. Internally the SharedPreferences are saved to a File. But I guess that this is not what you are looking for since it would still require you to hardcode all initial values.
Option 3: Something fancy
If you want I can also slap something fancy together like this:
#Resource(id = R.string.some_string)
private String someString;
#Resource(id = R.integer.some_integer)
private int someInteger;
As you can see this uses reflection and annotations to load resource values. Could be very convenient and might be exactly what you are looking for. You load all the annotated values from one Object by calling this:
ResourceLoader.load(context, object);
The source code of this ResourceLoader is nothing fancy, but currently it only supports loading string and integer resources, but it should be no problem to expand this:
public class ResourceLoader {
// This is the definition of the #Resource annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Resource {
public int id();
}
public static void load(Context context, Object target) {
final Class<?> cls = target.getClass();
// This gets all declared fields, meaning all inherited fields are ignored
final Field[] fields = cls.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
final Class<?> type = field.getType();
// We check if the Annotation is present
if(field.isAnnotationPresent(Resource.class)) {
final Resource annotation = field.getAnnotation(Resource.class);
final int id = annotation.id();
// And if it is present use the id and type of the field to load
// the correct resource
final Object value = loadValue(context, id, type);
try {
// Finally we set the new value to the field
field.set(target, value);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not set resource value to field " + field, e);
}
}
}
}
private static Object loadValue(Context context, int id, Class<?> type) {
final Resources resources = context.getResources();
if(int.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) {
return resources.getInteger(id);
}
if(String.class.isAssignableFrom(type)) {
return resources.getString(id);
}
throw new IllegalStateException("Type \"" + type + "\" is not supported!");
}
}
I have two public methods that I'd like to trace. One of the methods calls the other repeatedly. What I'd like to do is trace only the method that was called from the outside.
Here's a simple class to demonstrate what I mean:
public class LoggingExample {
private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class);
public static final String USER_ROOT = "/home/waisbrot";
/** could be called by fileExistsRobust *or* from outside */
public static boolean fileExists(String filename) {
logger.trace("Checking for file {}", filename);
File f = new File(filename);
return f.exists();
}
/** always gets called from outside */
public static boolean fileExistsRobust(String filename) {
logger.trace("Checking for any varient of {}", filename);
if (fileExists(filename))
return true;
for (String prefix : prefixes) { // this list is 100 items long
if (fileExists(prefix + filename));
return true;
}
return false;
}
}
Elsewhere in my code, I might call fileExists, in which case I want its logging message to get printed (assuming I'm tracing it). But if I call fileExistsRobost than I want that log message, but not fileExists.
I want to have both methods traced, but I'm getting buried in output when I call the second one. I was hoping Logback could be configured to understand what I want, but I'm not seeing anything useful in the documentation. I could flip a flag when I enter fileExistsRobust and then test for it in fileExists, but that's going to get ugly with more than one thread (since these are static methods) and it seems like it starts polluting the class with lots of logging infrastructure. I could use MDC to store the info, but that seems like an abuse of MDC.
Anyone run into this situation before? How'd you deal with it?
I assume that you are able to change the code. Then the simplest way in my opinion is avoiding the problem by introducing another internalFileExists(String filename) or overloading fileExists(String filename) with a logging toogle:
public static boolean fileExists(String filename, boolean doLog) {
if (doLog) logger.trace("Checking for file {}", filename);
File f = new File(filename);
return f.exists();
}
and let fileExistsRobust use the overloaded version with doLog = false, while the single argument version redirects to fileExists(filename, true).
That does not really address the problem, but mitigates it.