log4net configuration in .NET 3.5
Step 1 Add Reference
Right click on the "References" folder and choose add reference. Browse to the location of log4net and add it to the project.
Step 2 Add Config Section Reference
In web.config, add a reference to the log4net config section handler. This will look like:
<?xml version="1.0"?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/> <!-- other sections/sectionGroups --> </configSections> </configuration>
Step 3 Add the log4net configuration Section
All that is left is to add the actual configuration. There are an unlimited number of ways this can be done, with numerous different logs and log types. An example configuration follows. This configuration creates a log for NHibernate with a max size of 8MB, a rolling file (max size 3MB), and a a console log. Anything logged to the logger named NHibernate.SQL will record only if they are ERROR level or higher. Anything logged without specifying a logger name is logged to root, and only goes to the console and rolling file.
<log4net>
<appender name="NHibernateFileLog" type="log4net.Appender.RollingFileAppender,log4net">
<file value="logs/nhibernate.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="0"/>
<maximumFileSize value="8MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%d [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p %c{1}:%L - %m%n"/>
</layout>
</appender>
<!-- log4net uses 5 levels, namely DEBUG, INFO, WARN, ERROR and FATAL. -->
<appender name="rollingFile" type="log4net.Appender.RollingFileAppender,log4net">
<param name="File" value="logs/RollingLog.txt"/>
<param name="AppendToFile" value="false"/>
<param name="RollingStyle" value="Date"/>
<param name="DatePattern" value="yyyy.MM.dd"/>
<param name="StaticLogFileName" value="true"/>
<maximumFileSize value="3MB"/>
<maxSizeRollBackups value="0"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<logger name="NHibernate.SQL" additivity="false">
<level value="ERROR"/>
<appender-ref ref="NHibernateFileLog"/>
<appender-ref ref="console"/>
<appender-ref ref="rollingFile"/>
</logger>
<root>
<level value="ERROR"/>
<appender-ref ref="console"/>
<appender-ref ref="rollingFile"/>
<!--Uncomment the following appender for verbose output (degrades performance)-->
<!--<appender-ref ref="rollingFile"/>-->
</root>
</log4net>
Step 4
Now, all that's left is to call log4net's configure method before any other (read: **error prone**) code is called.
For example, in an ASP.NET Web application, you could add this to Global.asax:
public class GlobalAsax : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
}
}
Other logging options include database, mail, net, access.
For more information and examples on log4net, visit http://logging.apache.org/log4net/release/config-examples.html
An example Logger implementation in C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using log4net;
using log4net.Config;
namespace ETeacherWeb.Common
{
/// <summary>
/// Logger class for log4net programmatic logging
/// </summary>
public static class Logger
{
/// <summary>
/// Severity/Level of the log entry
/// </summary>
public enum LogLevel
{
DEBUG = 1,
ERROR,
FATAL,
INFO,
WARN
}
#region Members
private static readonly ILog logger = LogManager.GetLogger(typeof(Logger));
#endregion
#region Constructors
static Logger()
{
XmlConfigurator.Configure();
}
#endregion
#region Methods
/// <summary>
/// Write a string to the log with a specified level of severity
/// </summary>
/// <param name="logLevel">The severity of the log entry</param>
/// <param name="log">The log entry</param>
public static void WriteLog(LogLevel logLevel, String log)
{
if (logLevel.Equals(LogLevel.DEBUG))
{
logger.Debug(log);
}
else if (logLevel.Equals(LogLevel.ERROR))
{
logger.Error(log);
}
else if (logLevel.Equals(LogLevel.FATAL))
{
logger.Fatal(log);
}
else if (logLevel.Equals(LogLevel.INFO))
{
logger.Info(log);
}
else if (logLevel.Equals(LogLevel.WARN))
{
logger.Warn(log);
}
}
#endregion
}
}
To use the above code, you would call
Logger.WriteLog(LogLevel.DEBUG, "The expected logic failed validation");
Google Chrome Extension: New Tab Redirect! STEP BY STEP
Background
(see http://code.google.com/chrome/extensions/overview.html for a thorough background of Chrome Extension development)
A Google Chrome Extension is a mini web application which runs in its own process and can perform actions at a browser-level or page-level.
An extension consists (or may consist) of a number of parts:
- manifest.json
- Web files (html, js, css, images)
- NPAPI Plugins
- background.html (the long-running process)
All of these files are bundled into a package with a .crx file extension. When uploading your extension to the Google Chrome Extensions Gallery, you only zip up the files your extension needs and Google will package it properly for you.
The manifest.json file is a file which specifies properties of your extension in JavaScript Object Notation (aka JSON). For a quick intro to JSON, check out http://www.learn-ajax-tutorial.com/Json.cfm
The Manifest File
manifest.json:
{
"name": "New Tab Redirect!",
"description": "Sets a user-specified URL to load in new tabs.",
"version": "0.0.1.109",
"background_page": "background.html",
"options_page":"options.html",
"chrome_url_overrides": {
"newtab": "redirect.html"
},
"permissions": ["tabs"],
"icons": {
"128": "icon128.png",
"19":"icon19.png"
}
}
(end manifest.json)
The name, description, version, background_page, and options_page key/value pairs are pretty self-explanatory.
The "chrome_url_overrides" section is interesting. As you can see, the value for this entry is itself an object. This object allows me to override "newtab" with my own page, "redirect.html". Currently, the chromium team will only allow you to override newtab, however, I anticipate they'll allow you to override other pages in the future. For a list of url constants used within Chrome, check out the url_constants.cc file in the source code repository. Possible future overrides would be anything that is listed near the end of that file as 'const char kChromeUI*Host[]'
Back to the file. "permissions" takes an array of requested permissions. New Tab Redirect! requires permissions to update tabs. This is because we're not only redirecting to html web-hosted sites; New Tab Redirect allows you to set web pages, local files, or inherent Chrome pages.
"icons" specifies the location of the icons your extension will use. These icons will be displayed in the Gallery and in the Extensions page within Google Chrome.
Remember: You must change your version number when updating your publicly hosted extension, or else Chrome won't find a new version and your users will not be updated.
The Background Page
background.html
<html>
<head>
<script type="text/javascript">
String.prototype.startsWith = function(str){
return (this.indexOf(str) === 0);
}
var url = null;
var newTabId = undefined;
var protocol = undefined;
var allowedProtocols = ["http://","https://","about:","file://",
"file:\\", "file:///", "chrome://","chrome-internal://"];
function setUrl(url) {
if(protocolPasses(url)
&& url.length > { /* 8 is arbitrary */
this.url = url;
} else {
protocol = 'http://'; /* force http */
var right = url.split('://')
if(right != undefined && right != null && right.length > 1) {
this.url = protocol + right[1];
} else {
/* this will redirect to http:// if url is empty */
this.url = protocol + url; }
}
localStorage["url"] = this.url;
function protocolPasses(url) {
if(typeof(url) == 'undefined' || url == null) { return false; }
if (url.startsWith(allowedProtocols[3])
&& !url.startsWith(allowedProtocols[5])) {
url.replace(allowedProtocols[3], allowedProtocols[5]);
} else if (url.startsWith(allowedProtocols[4])) {
url.replace(allowedProtocols[4], allowedProtocols[5]);
}
for(var p in allowedProtocols) {
if(url.startsWith(allowedProtocols[p])) { return true;}
}
return false;
}
}
function init() {
url = localStorage["url"] || "http://www.facebook.com";
}
function r(tabId) {
chrome.tabs.update(tabId, {"url":this.url});
}
chrome.tabs.onUpdated.addListener(function(tabId,info,tab) {
if (info.status === 'loading')
console.log('Loading url ... ' + tab.url)
if(info.status === 'complete')
console.log('Finished loading url ... ' + tab.url)
});
chrome.tabs.onCreated.addListener(function(tab) {
newTabId = tab.id
});
</script>
</head>
<body onload="init()"></body>
</html>
(end background.html)
This page is pretty self-explanatory for the most part. The background page is your long-running process, and is usually used to house any variables that are shared between pages. However, this isn't a necessity of Chrome Extension architecture. Every page in an extension can interact with any other page through chrome.extension.getViews() or chrome.extension.getBackgroundPage() . Because of the nature of New Tab Redirect, I've used a background page.
One thing of note is the initialization of the extension. Since the operation is fairly simple, allowing a user to specify a url and then redirecting to that url, the only thing I need to worry about immediately is the url. This is stored using HTML 5's local storage. If the url doesn't exit, I set it to facebook.
The setUrl function is located in the Background page and is called from options.html. It is located here so the options page isn't trying to set the url variable that is maintained by the background page. This function checks my array of allowed protocols and validates the url with some simple rules.
r(tabId) is the function which actually updates the tab when the protocol isn't an http or https protocol. The function name is short because the the code in redirect.html should be as concise as possible. This function calls the chrome.tabs.update method (one culprit behind the tabs permission requirement)
That tabId is provided via the redirect.html, which gets the currently created tab's id. However, again note that this processing only occurs when the url is local.
Options
Because the options page is simple html and styles, I'll only include the JavaScript which sets the url.
options.js
String.prototype.startsWith = function(str) {
return (this.indexOf(str)===0);
}
var chromePages = {
Extensions : "chrome://extensions/",
History : "chrome://history/",
Downloads : "chrome://downloads/",
NewTab : "chrome-internal://newtab/"
}
var aboutPages = ["about:blank","about:version", "about:plugins","about:cache",
"about:memory","about:histograms","about:dns",
"chrome://extensions/","chrome://history/",
"chrome://downloads/","chrome-internal://newtab/"];
var popularPages = {
"Facebook":"www.facebook.com",
"MySpace":"www.myspace.com",
"Twitter":"www.twitter.com",
"Digg":"www.digg.com",
"Delicious":"www.delicious.com",
"Slashdot":"www.slashdot.org"
};
// save options to localStorage.
function save_options() {
var url = $('#custom-url').val();
if(url == ""){
url = aboutPages[0];
}
if( $.inArray(String(url), aboutPages) || isValidURL(url)) {
save(true,url);
} else {
save(false,url);
}
}
function save(good,url) {
if(good) {
chrome.extension.getBackgroundPage().setUrl(url);
$('#status').text("Options Saved.");
} else {
$('#status').text( url + " is invalid. Try again (http:// is required)");
}
$('#status').css("display", "block");
setTimeout(function(){
$('#status').slideUp("fast").css("display", "none");
}, 1050);
}
// Restores select box state to saved value from localStorage.
function restore_options() {
var url = chrome.extension.getBackgroundPage().url || "http://www.facebook.com/";
$('#custom-url').val(url);
}
function isValidURL(url) {
var urlRegxp = /^(http:\/\/www.|https:\/\/www.|ftp:\/\/www.|www.){1}([\w]+)(.[\w]+){1,2}$/;
if (urlRegxp.test(url) != true) {
return false;
} else {
return true;
}
}
function saveQuickLink(url){
var uurl = unescape(url);
$('#custom-url').val(uurl);
save(true,uurl);
return false;
}
$(document).ready(function(){
restore_options();
$.each(chromePages, function(k,v) {
var anchor = "<a href=\"javascript:saveQuickLink('"+v+"');\">"+k+"</a>";
$('#chromes').append("<li>" + anchor + "</li>");
});
$.each(aboutPages, function() {
if(this.startsWith("about:")) { /* quick fix to handle chrome pages elsewhere */
var anchor = "<a href=\"javascript:saveQuickLink('"+this+"');\">"+this+"</a>";
$('#abouts').append("<li>" + anchor + "</li>");
}
});
$.each(popularPages, function(k,v) {
var anchor = "<a href=\"javascript:saveQuickLink('"+v+"');\">"+k+"</a>";
$('#popular').append("<li>" + anchor + "</li>");
});
});
(end options.js)
As you can see, I've included jQuery for dom manipulation. I decided to do this because jQuery is so small and because the Options page can be whatever size you'd like (keeping in mind user experience, of course). This saved me some time from writing out a little bit of javascript.
Of note here is how I build the lists of Chrome pages, About pages, and Popular pages. This isn't included in the options.html file, which only has placeholders for the unordered lists. This allows me to go in and provide a page name and url for chromePages and popularPages, or just the url for aboutPages. If I want to add anything in the future, I'll just add a key/value pair (or url) to one of these objects. If I want to remove, likewise, I only touch the respective object.
Quick links (one from an above-mentioned object) are saved automatically, bypassing the url checking regex. However, you'll notice in the save_options function that I check first to see if the url is in the list of about pages, this is because a user will most likely enter about:memory or any valid url, and whould be less likely to enter chrome-internal://newtab/. In the future, I will probably add checks on all objects before checking a valid url, but this kind of thing happens in incremental development.
As you can see, if the user gets to save the url, the function setUrl from the background page is called via chrome.extension.getBackgroundPage().setUrl(url); Following good user interface techniques, we have to let the user know what happened, which is what the next line and the rest of the function accomplishes.
Note: The new tab url is chrome-internal:// instead of chrome://. Why is this, do you think? Don't think too hard, though. It's simple: redirect.html is now chrome://newtab/, because I've told chrome to override the newtab with my own file. You can test it out by typing chrome://newtab into Google Chrome. Try chrome-internal://newtab and see what happens? This can only be called internally, hence the name, and must be called from within an extension via chrome.tabs.update. If you don't use this internal url, when you set the url to chrome://newtab and hit CTRL+T or click the '+' tab, your tab will keep trying to call itself, and eventually stop, after doing nothing meaningful.
Redirect.html
Finally, the beast that does it all.
redirect.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta name="generator" content=
"HTML Tidy for Windows (vers 14 February 2006), see www.w3.org">
<title>
Redirecting...
</title>
<script type="text/javascript">
var wid, tid;
function r() {
var url = localStorage["url"] || "";
if (url.toLowerCase().indexOf("http:") === 0 ||
url.toLowerCase().indexOf("https:") === 0) {
document.location.href = url;
return;
} else {
chrome.windows.getCurrent(function(w) {
wid = w.id;
chrome.tabs.getSelected(wid, function(t) {
tid = t.id;
});
});
chrome.extension.getBackgroundPage().r(tid);
}
}
</script>
</head>
<body onload="setTimeout('r()',100);r();return false;">
Redirecting...<br>
<em>If your page doesn't load within 5 seconds,
<a href="javascript:r()">click here</a></em>
</body>
</html>
(end redirect.html)
Note: I have run the redirect.html file through HTML Tidy and a JavaScript formatter at http://jsbeautifier.org/, but this file should be as compact as possible. If you were to download New Tab Redirect and look at the source code, you'd see this code is all on one line. I'm writing this for your enjoyment, though, and one-liners aren't fun to look at.
The only thing special about this file is the the onload function. You'll notice how it runs r after 100 ms, then runs it again. There is quite an odd situation here: Chrome either has to initialize localStorage, doesn't allow us to get the current window and tab id immediately, or the processing required to do so takes longer than 100ms. You could, honestly, run the redirect function through a loop until it redirects (considering the document.location.href will immediately redirect the browser tab. If the url specified by the user is local, the function gets the current window, and from that the id of the current tab, and passes that to the background page's function.
I've been asked by a user or two to remove the redirect message, but I'll keep it for now. I consider a message necessary if, for some reason, a future release of Chrome breaks the extension.
The current source code for New Tab Redirect can be accessed via your Chrome Extensions directory after installing the extension, and is availing at the New Tab Redirect Project Page on Google Code.
If you have any questions or comments about this extension, please contact me.
My Development Blog
When I began my internship with Agility Healthcare Solutions (now part of GE Healthcare), I thought it would be beneficial to keep a blog of my activities. Originally, I intended to keep a blog of problems I found or areas I found difficult, in case I ever encountered these problems again. Then, I realized my blog could be beneficial to others if I post random code. Earlier stages of my blog focus on difficulties I have had, while later stages are just examples of my code.
Please, feel free to follow my blog and make comments!
The Link: Blogger: James Schubert
Quake Tracker
The Project
I love Google products! Again, I am a huge fan of Google products, so I decided to explore the APIs and tools which Google offers.
Google Maps API is a framework which allows you to use Google's Maps in your own site, overlaying your own content onto the map. I wanted to create a map for a long time, but I didn't have any ideas for what I could overlay.
The Solution
When I was younger, I wanted to be a seismologist. I think this originated from the Pierce Brosnan movie, "Dante's Peak". Recently, my wife and I watched this film and it was not as factual as I once believed. Perhaps the best part of the movie, was when Brosnan, a volcanologist, is in the truck with the town's mayor. She is driving and approaches a river of lava. She asks, "Do you think it's safe to drive over it?" Brosnan says, "I don't know!" Wow!
With that background, I think it's understandable that I wanted to track earthquakes! Although this project isn't finished, I think it is fun to open it and see where the earth is quaking. This page uses jQuery and pulls an XML feed for earthquakes between M2 and M5 from usgs.gov, which is updated regularly. That means what you see is always up-to-date!
The Link: Quake Tracker
Calculator (Java/GWT)
The Project
My continuing goal in application development is to learn new technologies and explore my possibilities. I am a huge fan of Google products, so I decided to explore the APIs and tools which Google offers.
Google Web Toolkit is a framework which allows you to write web content in Java, and convert the code to JavaScript and HTML designed to work the same across browsers. I really enjoy using frameworks which make development faster and easier, such as Google Web Toolkit and jQuery.
This was my first attempt at creating anything with Google Web Toolkit. After following the example tutorials on Google's site, this was very easy.
The Link: Open in New Window/Tab or Open as pop-up
Meta Tag Generator
The Project
My wife and her friend are starting a copy writing business. I wrote this tool to help with SEO. Although simple, the application was written to make html code generation point-and-click.
This is a Web Start Application written in Java. It's fairly simple, with a Swing interface. It's not meant to include all meta tag possibilities. If you have any questions please feel free to contact me.
The Link: Meta Tag Generator
INFO 465 – Projects in IS
The class:
"The student's behavioral and technical skills developed in listed prerequisite courses are challenged by participating in a team systems development project. Appropriate computer-assisted software engineering (CASE) tools are used throughout the project, from requirement specification to implementation and testing." (VCU Course Description)
The assignment:
The semester project was to create a site for a two-man landscaping company. The assignment is designed to allow students to better understand EDI concepts, requirements gathering, inventory and customer management, double-entry accounting, and other real-world topics.
Many students decided to use WinForms on Access databases. To switch things up, I used ASP.NET with C# code on a SQL Server 2005 database. The site may look very simple, the reason for this is a requirement to have our applications be presentable on small screens such as netbooks or PDAs. While my site is not explicitly designed for mobile devices, it does look good on small screens such as iPod Touch.
The site which is currently hosted is not the complete site. In fact, it is the product about 2/3 the way through the semester. At this point in the semester, we were given another project. I developed both sites on my local machine, and never uploaded the final product. After graduation, I have been unable to upload the final project.
You may log into the system using the username "ralph" and the password "cheese".
The Link: INFO 465 Example (site currently down)
INFO 451 – ASP.NET
The class:
INFO 451 - ASP.NET sought to teach advanced e-commerce practices using ASP.NET and C#.
The assignment:
This was a semester-long group assignment, aimed at honing the skills of each individual, while teaching the real-world connection between all aspects of Information Systems: people, technology, and organization.
The solution:
My group and I decided to offer an online bookseller as our project. Because most teams had two group members, and ours had three, we decided to go the extra step and offer retail books and user-submitted stories.
There was a huge difference in levels of programming knowledge in our group, which slowed things down. However, I am confident to say that our finished product surpassed that of the other groups.
What stands out?
Our site uses an MVC/Factory pattern (not ASP.NET MVC Framework, however). The data access methods are abstracted for modularity, meaning a switch from SQL Server 2005 to Oracle or Access would only require rewriting the fundamental database operations class, instead of each object's data access layer. Unfortunately, time constraints lead to mixing some of this code into the Data Access Layer. This would have been changed after graduation, but the passwords to access the host server were immediately deactivated.
The Link: INFO 451 Example
INFO 300 – Hardware & Software
The class:
Principles of computer hardware and software architecture, organization and operation. Introduction to data structures.
The assignment:
You assignment is to write a short research paper with a minimum of three references on the topic of web accessibility. Additionally you are to construct website in your web space so that it launches from your link at info300.net and works appropriately with whatever 'web page reader' software you've loaded on your PC or notebook computer. Your home page should have a link to your short paper.
On or before the due date for your assignment place the topic, outline, and references for it in a directory just off your home directory named Outlines. Place the title & topic for the brief on the first line of a file named Brief1, then double-space and follow it with the outline and references, using indentation to highlight the structure of your document. Use chmod to set the permissions on the Outlines directory to 700 and the file Brief1 to 600. For the website part of your project, make a directory named 'web' just off your home directory. Set its permissions to 701. Your 'home page' must be in the web directory, named exactly 'index.html'. The permissions should be set to 604 for each of the web pages. There should be at least one other page with the technical brief that links back to your home page.
The Link: INFO 300 Example
