2

I have a common logging project that uses log4net to record logging events. Since it's a common project, it's configured in a non-standard way: almost everything is set via C# code.

Now, I have an AdoNet appender configured in this manner, which includes logging the method that triggered the log event. It is created as part of logging initialization and it's defined as follows:

appender.AddParameter(new AdoNetAppenderParameter()
{
    ParameterName = "@Method",
    DbType = DbType.String,
    Size = 255,
    Layout = new RawLayoutConverter().ConvertFrom(new PatternLayout("%method")) as IRawLayout
});

Since I'm tying into the log4net %method property, this will automatically pull the method name that triggered the logging event and send it to the database in a parameter called @Method which will eventually be inserted into a database table that has a column called Method.

However, I'm adding some new functionality to globally handle exceptions and log them. When an exception bubbles up to the top of the call stack from anywhere, it will be passed to this new global method so it can be logged. I have access to the exception, so I can see the controller and method that caused this exception. I could easily add this as a log4net custom property (mapped to something like %property{ExceptionMethod}). My problem is overriding (or overwriting) log4net's %method property with my own custom property.

So, how can I have log4net conditionally choose between %method and %property{ExceptionMethod} when sending the data through the AdoNet appender to log this information under the Method column of my logging database table? Is this even feasible?

Ellesedil
  • 1,576
  • 1
  • 20
  • 44

2 Answers2

2

Without knowing exactly how you're confuguring and using log4net, it's hard to write code which will fit into your existing framework: this example though creates two appenders, one that uses %method and one that uses %property{ExceptionMethod}, and assigns them to different loggers:

public abstract class BaseAppender : AdoNetAppender
{
    protected BaseAppender()
    {
        // Add common parameters, set connection strings etc

        // e.g.
        this.AddParameter(new AdoNetAppenderParameter
        {
            ParameterName = "@log_level",
            DbType = DbType.String,
            Size = 50,
            Layout = new RawLayoutConverter().ConvertFrom(new PatternLayout("%level")) as IRawLayout
        });

        // Then ask each subclass to add the extra parameters
        this.AddExtraParameters();
    }

    protected abstract void AddExtraParameters();
}

public class RuntimeAppender : BaseAppender
{
    protected override void AddExtraParameters()
    {
        this.AddParameter(new AdoNetAppenderParameter
        {
            ParameterName = "@Method",
            DbType = DbType.String,
            Size = 255,
            Layout = new RawLayoutConverter().ConvertFrom(new PatternLayout("%method")) as IRawLayout
        });
    }
}

public class UnhandledExceptionAppender : BaseAppender
{
    protected override void AddExtraParameters()
    {
        this.AddParameter(new AdoNetAppenderParameter
        {
            ParameterName = "@Method",
            DbType = DbType.String,
            Size = 255,
            Layout =
                new RawLayoutConverter().ConvertFrom(new PatternLayout("%property{ExceptionMethod}")) as IRawLayout
        });
    }
}

public sealed class RuntimeLogger : Logger
{
    public RuntimeLogger(string name)
        : base(name)
    {
        this.Appenders.Add(new RuntimeAppender());
        this.Level = Level.Error; // etc
    }
}

public sealed class UnhandledExceptionLogger : Logger
{
    public UnhandledExceptionLogger(string name)
        : base(name)
    {
        this.Appenders.Add(new UnhandledExceptionAppender());
        this.Level = Level.Error; // etc
    }
}

Then, at runtime, you choose which to use:

public class ExceptionHandler
{
    public void HandleException(Exception ex)
    {
        string exceptionMethod = "set exception method here" ; 
        GlobalContext.Properties["ExceptionMethod"] = exceptionMethod;

        var logger = new UnhandledExceptionLogger("Logger Name Goes Here");
        logger.Log(Level.Error, "Message", ex);
    }
}

public class RuntimeLogging
{
    public void LogSomething(Exception ex)
    {
        var logger = new RuntimeLogger("Logger Name Goes Here");
        logger.Log(Level.Error, "Message", ex);
    }
}   
stuartd
  • 70,509
  • 14
  • 132
  • 163
  • This is certainly feasible. For what it's worth, I decided to just add a second column that's mapped to `%property{ExceptionMethod}` (although I've renamed it). But if I were really keen on avoiding a second method column that would contain a lot of `null` records, I'd probably pursue this solution. – Ellesedil Feb 24 '15 at 14:58
0

Depending on your needs, you might find it easier to implement your AdoNetAppender entirely in code.

There's at least one way to do that, found in my earlier answer here.

Community
  • 1
  • 1
InteXX
  • 6,135
  • 6
  • 43
  • 80
  • Please don't copy code your other answer. Also this doesn't answer the question. – stuartd Feb 24 '15 at 11:16
  • @stuartd: Hmm, I only copied it to be helpful... so OP (and other readers) could save a little time by not having to click through. Is this sort of helpfulness frowned upon? If I edit to remove the code, will that inspire you to give back the downvote? ;-) OP: "So, how can I have log4net conditionally choose..." My answer precisely answers this question. Write code to conditionally choose the pattern. Are you certain I didn't answer the question? – InteXX Feb 24 '15 at 11:23
  • It doesn't answer the question because OP needs to use different `method` parameter values at runtime - your code loads a single DBAppender, which doesn't have a method parameter at all. Also note OP has stated "almost everything [in the log4net configuration] is set via C# code" already. – stuartd Feb 24 '15 at 11:30
  • @stuartd: Goodness, you're right it doesn't! I'm pretty certain my code could be refactored to accomplish the task, but I'd have to test it before claiming that it worked (naturally). So how 'bout that edit/downvote thing? – InteXX Feb 24 '15 at 11:37
  • @stuartd: Thanks :-) I've been over on Meta, reading about downvotes... I guess a lot of newcomers (such as myself) are having a hard time not taking them too personally. It's all a part of learning SO, I guess. – InteXX Feb 24 '15 at 11:57
  • @stuartd: Your comment to OP is the solution and is what I would've ended up doing. I'm curious; why not add it as an answer here? – InteXX Feb 24 '15 at 12:11
  • @stuartd: Now *that* I can understand! OK, I'll leave you to your work... thanks for your help. – InteXX Feb 24 '15 at 12:12
  • As another mentioned, I'm already doing this. Also, this answer doesn't address how to pick one property over another at runtime. – Ellesedil Feb 24 '15 at 14:46
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – David says Reinstate Monica Feb 24 '15 at 16:47
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. – Jonathan Nixon Feb 24 '15 at 19:03
  • @DavidGrinberg: Thanks for the pointer—I was aware of this, but on stuartd's prompting I decided anyway to edit it down to a link-only answer, since the link points to another page on SO. Links to SO pages should be OK, yes? – InteXX Feb 24 '15 at 20:53
  • @Guthwulf: Thanks—as you can see in these comments, I'd first thought it was but upon stuartd's prompting I looked closer to see that it isn't. Are you of the opinion that I should delete the answer and this comment thread? – InteXX Feb 24 '15 at 20:55
  • @Ellesedil: Looks like stuartd got it for you; you'll want to go with his answer. After some discussion with him, I realized that my code wouldn't work for you. In the end, I would've ended up doing the same thing as he. – InteXX Feb 25 '15 at 05:47