The basis of using Cascading Style Sheets (CSS) to apply formatting to an HTML document is deceptively easy to understand, but hard to master. You define selectors that designate HTML elements, which contains formatting rules that apply to those elements and their children. If more than one rule applies to an element, they are applied using a priority order, from the more general to the more specific: that’s where the “cascading” part comes in.
It’s almost impossible to predict how a website will look by looking at the code. If you have to modify the CSS of an application you don’t know “just” to change a color or a font size using only your text editor, you’re out of luck. Figuring out the priority of each selector is complicated if you have many stylesheets that applies to a page, and you can easily miss a more general selector. You can’t just look at an element on the page and immediately deduce which CSS rules are affecting it, especially if there are many contradictory rules.
That’s where tools such as Firebug for Firefox or the Developer Tools in Chrome comes in. Among many other great features, they allow you to see which rules are affecting a particular element on the page, and to modify them on the fly.
Of course, it won’t modify your original CSS since it only changes the local copy in your browser, but you can quickly test what’s the impact on your page and then make the modifications. You can also use those tools to explore your favorite websites and see how they used CSS to format elements: it can be a great learning tool if you’re looking to grow your CSS knowledge.
Here is a screencast on how to use Firebug to figure out where the colors are defined on the Twitter website:
You can have more than one Web.config in an application and they will all be merged in the end, which is fine. Also, if you have a problem with the configuration for a given project it will usually show up in your development environment right away and will not make it to production.
But here is a problem that occurred to me a few times and that had me pretty stumped at first. When deploying an ASP.NET MVC or ASP.NET WebForms application to an IIS (Internet Information Service) server, it looked like configurations from unrelated Web.config or other .config files merged together. Errors were popping up from assembly versions or configurations that should not even be used in the new application! So what were those mystery configurations?
In IIS, the Sites folder is a container for all the applications and virtual directories of the server. The Sites folder has a physical root directory even it if doesn’t contain files specific to an application. In fact, configurations files from that folder are merged with the configuration of all your applications. Here is the configuration in IIS for the physical path of that folder:
In my case, the root folder pointed to another application on the same server, so the configurations made no sense for the new application. To solve the problem, I just made sure than the wwwroot folder was empty or contained only configurations that I intended to share with all the applications hosted on the server.
For references purpose, here is the configuration for the physical path of each individual application. You don’t need to point to a folder under wwwroot as long as the folder used has the proper permissions to be seen from the web.
For Android applications, logging is handled by the android.util.Log class, which is a basic logging class that stores the logs in a circular buffer for the whole device. All logs for the device can be seen in the LogCat tab in Eclipse, or read using the logcat command. Here is a standard log output for the LogCat tab in Eclipse :
There are five levels of logs you can use in your application, for the most verbose to the least verbose :
Verbose:For extra information messages that are only compiled in a debug application, but never included in a release application.
Debug: For debug log messages that are always compiled, but stripped at runtime in a release application.
Info: For an information in the logs that will be written in debug and release.
Warning: For a warning in the logs that will be written in debug and release.
Error: For an error in the logs that will be written in debug and release.
A log message includes a tag identifying a group of messages and the message. By default the log level of all tags is Info, which means than messages that are of level Debug and Verbose should never shown unless the setprop command is used to change the log level. So, to write a verbose message to the log, you should call the isLoggable method to check if the message can be logged, and call the logging method :
if (!Log.isLoggable(logMessageTag, Log.Verbose))
Log.v("MyApplicationTag", logMessage);
And to show the Debug and Verbose log message for a specific tag, run the setprop command while your device is plugged in. If you reboot the device you will have to run the command again.
Unfortunately, starting with Android 4.0, an application can only read its own logs. It was useful for debugging to be able to read logs from another application, but in some cases sensitive information was written in those logs, and malicious apps were created to retrieve them. So if you need to have logs files send to you for debugging, you will need to create your own log class using the methods from the android.util.Log class. Remember, only Info, Warning and Error logs should be shown when the application is not run in debug mode. Here is an example of a simple logger wrapping the call to isLoggable and storing the logs messages on the primary storage of the device (requires the permission WRITE_EXTERNAL_STORAGE) and to the standard buffer :
/**
* A logger that uses the standard Android Log class to log exceptions, and also logs them to a
* file on the device. Requires permission WRITE_EXTERNAL_STORAGE in AndroidManifest.xml.
* @author Cindy Potvin
*/
public class Logger
{
/**
* Sends an error message to LogCat and to a log file.
* @param context The context of the application.
* @param logMessageTag A tag identifying a group of log messages. Should be a constant in the
* class calling the logger.
* @param logMessage The message to add to the log.
*/
public static void e(Context context, String logMessageTag, String logMessage)
{
if (!Log.isLoggable(logMessageTag, Log.ERROR))
return;
int logResult = Log.e(logMessageTag, logMessage);
if (logResult > 0)
logToFile(context, logMessageTag, logMessage);
}
/**
* Sends an error message and the exception to LogCat and to a log file.
* @param context The context of the application.
* @param logMessageTag A tag identifying a group of log messages. Should be a constant in the
* class calling the logger.
* @param logMessage The message to add to the log.
* @param throwableException An exception to log
*/
public static void e(Context context, String logMessageTag, String logMessage, Throwable throwableException)
{
if (!Log.isLoggable(logMessageTag, Log.ERROR))
return;
int logResult = Log.e(logMessageTag, logMessage, throwableException);
if (logResult > 0)
logToFile(context, logMessageTag, logMessage + "\r\n" + Log.getStackTraceString(throwableException));
}
// The i and w method for info and warning logs should be implemented in the same way as the e method for error logs.
/**
* Sends a message to LogCat and to a log file.
* @param context The context of the application.
* @param logMessageTag A tag identifying a group of log messages. Should be a constant in the
* class calling the logger.
* @param logMessage The message to add to the log.
*/
public static void v(Context context, String logMessageTag, String logMessage)
{
// If the build is not debug, do not try to log, the logcat be
// stripped at compilation.
if (!BuildConfig.DEBUG || !Log.isLoggable(logMessageTag, Log.VERBOSE))
return;
int logResult = Log.v(logMessageTag, logMessage);
if (logResult > 0)
logToFile(context, logMessageTag, logMessage);
}
/**
* Sends a message and the exception to LogCat and to a log file.
* @param logMessageTag A tag identifying a group of log messages. Should be a constant in the
* class calling the logger.
* @param logMessage The message to add to the log.
* @param throwableException An exception to log
*/
public static void v(Context context,String logMessageTag, String logMessage, Throwable throwableException)
{
// If the build is not debug, do not try to log, the logcat be
// stripped at compilation.
if (!BuildConfig.DEBUG || !Log.isLoggable(logMessageTag, Log.VERBOSE))
return;
int logResult = Log.v(logMessageTag, logMessage, throwableException);
if (logResult > 0)
logToFile(context, logMessageTag, logMessage + "\r\n" + Log.getStackTraceString(throwableException));
}
// The d method for debug logs should be implemented in the same way as the v method for verbose logs.
/**
* Gets a stamp containing the current date and time to write to the log.
* @return The stamp for the current date and time.
*/
private static String getDateTimeStamp()
{
Date dateNow = Calendar.getInstance().getTime();
// My locale, so all the log files have the same date and time format
return (DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.CANADA_FRENCH).format(dateNow));
}
/**
* Writes a message to the log file on the device.
* @param logMessageTag A tag identifying a group of log messages.
* @param logMessage The message to add to the log.
*/
private static void logToFile(Context context, String logMessageTag, String logMessage)
{
try
{
// Gets the log file from the root of the primary storage. If it does
// not exist, the file is created.
File logFile = new File(Environment.getExternalStorageDirectory(), "TestApplicationLog.txt");
if (!logFile.exists())
logFile.createNewFile();
// Write the message to the log with a timestamp
BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true));
writer.write(String.format("%1s [%2s]:%3s\r\n", getDateTimeStamp(), logMessageTag, logMessage));
writer.close();
// Refresh the data so it can seen when the device is plugged in a
// computer. You may have to unplug and replug to see the latest
// changes
MediaScannerConnection.scanFile(context,
new String[] { logFile.toString() },
null,
null);
}
catch (IOException e)
{
Log.e("com.cindypotvin.Logger", "Unable to log exception to file.");
}
}
}
If you release an application to the app store or to a client with this kind of logger, you should disable logging by default and add a switch in the preferences to enable logging on demand. If the logger is always enabled, your application will often write to the primary storage and to the logcat, which is an unnecessary overhead when everything works correctly. Also, the size of the log file should be limited in some way to avoid filling up the primary storage.