Custom target not working - nlog

I am trying to implement the solution that was posted in response to this question but it is not working.
My objective is to log to a file (working) and also have the LogHandler methods fire (not working).
class Program
{
private static Logger Logger;
static void Main(string[] args)
{
Target.Register<CallbackTarget>("CallbackTarget"); // https://github.com/NLog/NLog/wiki/Register-your-custom-component
LogManager.Configuration.AddTarget("CallbackTarget", new CallbackTarget(LogHandlerA, LogHandlerB));
Logger = LogManager.GetCurrentClassLogger();
Worker.DoNothing();
Logger.Debug("Log msg from Program");
Console.ReadLine();
}
public static void LogHandlerA(string msg)
{
Console.WriteLine("LogHandlerA " + msg);
}
public static void LogHandlerB(string msg)
{
Console.WriteLine("LogHandlerB " + msg);
}
}
public class Worker
{
private static Logger Logger;
static Worker()
{
Logger = LogManager.GetCurrentClassLogger();
}
public static void DoNothing()
{
Logger.Debug("Log msg from DoNothing"); // should trigger callbacks
}
}
[Target("CallbackTarget")]
public sealed class CallbackTarget : TargetWithLayout
{
private readonly Action<String>[] _callbacks;
public CallbackTarget(params Action<string>[] callbacks)
{
this._callbacks = callbacks;
}
protected override void Write(LogEventInfo logEvent)
{
base.Write(logEvent);
foreach (var callback in _callbacks)
callback(logEvent.FormattedMessage);
}
}
Edit: adding nlog.config
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!--https://github.com/nlog/NLog/wiki/File-target#time-based-file-archival-->
<variable name="methodName"
value="${callsite:className=True:fileName=False:includeSourcePath=False:methodName=True:cleanNamesOfAnonymousDelegates=False:includeNamespace=False:skipFrames=0}" />
<targets>
<target name="file" xsi:type="File"
layout="${longdate} [${level:uppercase=true}] [thread ${threadid}] [${methodName}] ${message} "
fileName="${basedir}/logs/logfile.txt"
archiveFileName="${basedir}/archives/log.{#}.txt"
archiveEvery="Day"
archiveNumbering="Rolling"
maxArchiveFiles="7"
concurrentWrites="true"
keepFileOpen="false"
encoding="iso-8859-2" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="file" />
</rules>
</nlog>

Nlog has recently received some new features:
NLog ver. 4.5.8 introduces lambda for the MethodCallTarget
NLog ver. 4.6.4 introduces Logger.WithProperty that matches Serilog.ForContext. Alternative for the existing NLog.Fluent.LogBuilder.
using System;
using System.Runtime.CompilerServices;
using NLog;
using NLog.Fluent;
namespace ConsoleExample
{
static class Program
{
static void Main(string[] args)
{
var outputTemplate = "${longdate} ${level} ${logger}${newline}${message}${newline}in method ${event-properties:CallerMemberName} at ${event-properties:CallerFilePath}:${event-properties:CallerLineNumber}${NewLine}${exception:format=tostring}";
Action<string> handler = LogHandler;
// Setup NLog Config
var nlogConfig = new NLog.Config.LoggingConfiguration();
var customTarget = new NLog.Targets.MethodCallTarget("CustomTarget", (logEvent, parms) => CustomTargetLogger(logEvent, parms[0].ToString(), handler));
customTarget.Parameters.Add(new NLog.Targets.MethodCallParameter(outputTemplate));
nlogConfig.AddRule(NLog.LogLevel.Info, NLog.LogLevel.Fatal, customTarget);
NLog.LogManager.Configuration = nlogConfig;
// Start Logging
var nlogLogger = NLog.LogManager.GetCurrentClassLogger();
nlogLogger.Info().Message("Hello World").Write(); // NLog.Fluent.LogBuilder
nlogLogger.Here().Info("Hello World"); // Requires NLog ver. 4.6.4
Console.ReadLine();
}
public static void CustomTargetLogger(LogEventInfo logEvent, string outputTemplateResult, params Action<string>[] handlers)
{
foreach (Action<string> handler in handlers)
handler(outputTemplateResult);
}
public static void LogHandler(string logMsg)
{
Console.WriteLine("LogHandler: " + logMsg);
}
public static NLog.Logger Here(this NLog.Logger logger,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
return logger.WithProperty("CallerMemberName", callerMemberName)
.WithProperty("CallerFilePath", callerFilePath)
.WithProperty("CallerLineNumber", callerLineNumber);
}
}
}

Related

Azure Function - ILogger is not logging?

I have an azure function, it logs the information without any issues.
namespace aspnetcore_azurefun_blob
{
[StorageAccount("AzureWebJobsStorage")]
public class FileTrigger
{
#region Property
private readonly IFileProcessor fileProcessor;
#endregion
#region Constructor
public FileTrigger(IFileProcessor fileProcessor)
{
this.fileProcessor = fileProcessor;
}
#endregion
[FunctionName("FileTrigger")]
public void ProcessFilesFromSamplesContainer([BlobTrigger("samples-workitems/{name}")]Stream myBlob, string name, ILogger log, ExecutionContext context)
{
log.LogInformation("Function: ProcessFilesFromSamplesContainer is called");
log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
var result = fileProcessor.ProcessAsync(myBlob, name);
log.LogInformation($"Function Completed Successfully on {DateTime.Now.ToLongDateString()} # {DateTime.Now.ToShortTimeString()}.\n.");
}
However, I also have the business logic implemented using DI and below is the excerpt of the implementation.
ServiceBase.cs
namespace BusinessService.Services.Common
{
public abstract class ServiceBase<T>
{
public static AppDbContext AppDbContext;
public static ILogger<T> Logger { get; set; }
public static AppConfigurator Configurator { get; set; }
public ServiceBase(AppDbContext appDbContext, ILogger<T> logger, IOptions<AppConfigurator> configurator)
{
AppDbContext = appDbContext ?? throw new ArgumentNullException(nameof(appDbContext));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Configurator = configurator.Value ?? throw new ArgumentNullException(nameof(configurator));
}
}
}
FileProcessingService.cs
namespace BusinessService.Services
{
public interface IFileProcessingService
{
void Process(Stream myBlob, string name);
}
public class FileProcessingService : ServiceBase<FileProcessingService>, IFileProcessingService
{
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
}
#endregion
}
}
Line#34 and #35 doesn't Log anything
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
DependencyRegistrar.cs
namespace CrossCutting.DependencyInjection
{
public static class DependencyRegistrar
{
public static void Intialize(this IServiceCollection services)
{
// Initialize App Settings from Configurator Settings Json file
services.AddOptions<AppConfigurator>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("AppConfigurator").Bind(settings);
})
.Validate((c) =>
{
return !new[] { c.AzureSQLServerConfigurator.ConnnectionString }.Any(s => String.IsNullOrWhiteSpace(s));
});
}
}
}
What am I missing so that FileProcessingService.cs will log the information ?
I have checked your code in our end I could get the logging information. In your code i have noticed in your code you are using the Logger instead of logger.
Because in your Region Constructor you are using ILogger<FileProcessingService> logger from here you have to call the logger to push your logging information into Application Insights/Function execution panel (Output console window)
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
# changed Logger into logger
logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
logger.LogInformation("Database is updated..!");
}
#endregion
Still if not able to push the logs you can add your namespace in your host.json file to avoid missing the logging
{
"version": "2.0",
"logging": {
"logLevel": {
// Here you can use your Project namespace like BusinessService.Services
"<namespace>": "Information"
}
}
}

How to use dependency inject for TelemetryConfiguration in Azure Function

I try to use Dependency injection in Azure Functions for TelemetryConfiguration. In my function I will have it resolved when I inject TelemetryConfiguration in the functions constructor. I suppose I don't really understand how I will do with TelemetryConfiguration in StartUp, thats why I get an exception. How will I add the TelemetryConfiguration I already configured.
I have did an easy example here what I'm doing so far.
[assembly: FunctionsStartup(typeof(StartUp))]
public class StartUp : FunctionsStartup
{
private string OmsModule { get; } = "OMS.VA";
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.Configure<TelemetryConfiguration>(
(o) =>
{
o.InstrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
o.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
});
}
}
public class StopPlaceUpdateTimerTrigger
{
private TelemetryClient _telemetryClient;
private string _azureWebJobsStorage;
public StopPlaceUpdateTimerTrigger(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
[FunctionName("StopPlaceLoader")]
public async Task StopPlaceLoaderMain([TimerTrigger("%CRON_EXPRESSION%", RunOnStartup = true)]TimerInfo myTimerInfo, ILogger log, ExecutionContext context)
{
SetConfig(context);
var cloudTable = await GetCloudTableAsync();
if (cloudTable == null)
{
//Do nothing
}
//Do nothing
}
private async Task<CloudTable> GetCloudTableAsync()
{
var storageAccount = CloudStorageAccount.Parse(_azureWebJobsStorage);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference(nameof(StopPlaceLoaderCacheRecord));
if (!await table.ExistsAsync())
{
await table.CreateIfNotExistsAsync();
}
return table;
}
private void SetConfig(ExecutionContext context)
{
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true)
.AddEnvironmentVariables()
.Build();
_azureWebJobsStorage = config["AzureWebJobsStorage"];
}
}
//local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol...",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"EnableMSDeployAppOffline": "True",
"CRON_EXPRESSION": "0 */5 22-3 * * *",
"APPINSIGHTS_INSTRUMENTATIONKEY": "..."
}
}
I get the following Exception;
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration' while attempting to activate 'OMS.VA.RealTime.StopPlaceLoader.StopPlaceUpdateTimerTrigger'.
Update:
We can change this line of code var newConfig = TelemetryConfiguration.Active; to var newConfig = TelemetryConfiguration.CreateDefault(); , since TelemetryConfiguration.Active is deprecated.
Please use the code below for TelemetryConfiguration DI, I test it with a blob trigger function and works well:
using System.IO;
using System.Linq;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[assembly: WebJobsStartup(typeof(FunctionApp17.MyStartup))]
namespace FunctionApp17
{
public class MyStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var configDescriptor = builder.Services.SingleOrDefault(tc => tc.ServiceType == typeof(TelemetryConfiguration));
if (configDescriptor?.ImplementationFactory != null)
{
var implFactory = configDescriptor.ImplementationFactory;
builder.Services.Remove(configDescriptor);
builder.Services.AddSingleton(provider =>
{
if (implFactory.Invoke(provider) is TelemetryConfiguration config)
{
var newConfig = TelemetryConfiguration.Active;
newConfig.ApplicationIdProvider = config.ApplicationIdProvider;
newConfig.InstrumentationKey = config.InstrumentationKey;
return newConfig;
}
return null;
});
}
}
}
public class Function1
{
private TelemetryClient _telemetryClient;
public Function1(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
[FunctionName("Function1")]
public void Run([BlobTrigger("samples-workitems/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, ILogger log)
{
log.LogInformation($"!!!!!!!!!! C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
_telemetryClient.TrackTrace("this is a test message from DI of telemetry client !!!!!!!!!!!!!!");
}
}
}
the test result as below, I can see the logs in the application insights in azure portal:
And one more thing, I see you try to use ITelemetry Initializer in your code. You can follow this GitHub issue for your ITelemetry Initializer or Itelemetry Processor
If you use builder.Services.Configure<TelemetryConfiguration>() to configure, you are using Options pattern in ASP.NET Core.
To access the option, you need to do as following:
public StopPlaceUpdateTimerTrigger(IOptionsMonitor<TelemetryConfiguration> telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration.CurrentValue);
}
If you just want to directly use TelemetryConfiguration object, you need to add it in service collection:
builder.Services.AddSingleton<TelemetryConfiguration >(sp =>
{
var telemetryConfiguration = new TelemetryConfiguration();
telemetryConfiguration.InstrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
return telemetryConfiguration;
}
Then you can :
public StopPlaceUpdateTimerTrigger(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
Hope it helps.

Xamarin.iOS - System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation

When I add this code to my viewController in my Xamarin.iOS app:
public static MobileServiceClient MobileService = new MobileServiceClient(
"https://test-database.azure-mobile.net/",
"FxMRoOZYMaYFrJDZEhVOkziHEwzkqw26"
);
I get this error:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
How do I resolve this error?
Here is my whole ViewController:
using System;
using System.Drawing;
using Foundation;
using UIKit;
using Microsoft.WindowsAzure.MobileServices;
namespace MirrorMirror
{
public partial class RootViewController : UIViewController
{
public static MobileServiceClient MobileService = new MobileServiceClient(
"https://test-database.azure-mobile.net/",
"FxMRoOZYMaYFrJDZEhVOkziHEwzkqw26"
);
//private readonly IMobileServiceTable<ToDoItem> ToDoTable = MobileService.GetTable<ToDoItem>();
static bool UserInterfaceIdiomIsPhone
{
get { return UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone; }
}
public RootViewController(IntPtr handle) : base(handle)
{
}
public override void DidReceiveMemoryWarning()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
#region View lifecycle
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
}
#endregion
partial void RefreshButton_TouchUpInside(UIButton sender)
{
var ToDo = new ToDoItem();
ToDo.Text = "Buy Milk";
// ToDoTable.InsertAsync(ToDo);
}
}
}

SharePoint designer errors out with missing required parameters for custom activity

I am making a custom Activity for SharePoint 2010. When I drop the activity in the SharePoint Designer and supply values for the parameters, I get a mysterious validation error that simply says:
This action has required parameters that are missing.
Here's the source code for the action:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
using System.Workflow.ComponentModel.Compiler;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;
namespace SharePoint.Activities.IO
{
public partial class CopyFile : SequenceActivity
{
public CopyFile()
{
InitializeComponent();
}
public static DependencyProperty SourceDirectoryProperty = DependencyProperty.Register("SourceDirectory", typeof(string), typeof(CopyFile));
public static DependencyProperty TargetDirectoryProperty = DependencyProperty.Register("TargetDirectory", typeof(string), typeof(CopyFile));
public static DependencyProperty ActionResultProperty = DependencyProperty.Register("ActionResult", typeof(string), typeof(CopyFile));
public static DependencyProperty CreateDateProperty = DependencyProperty.Register("CreateDate", typeof(DateTime), typeof(CopyFile));
public static DependencyProperty CompletionDateProperty = DependencyProperty.Register("CompletionDate", typeof(DateTime), typeof(CopyFile));
public static DependencyProperty WFContextProperty = DependencyProperty.Register("WFContext", typeof(WorkflowContext), typeof(CopyFile));
[Description("Path the files to perform the operation on")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string SourceDirectory
{
get { return (string)GetValue(SourceDirectoryProperty); }
set { SetValue(SourceDirectoryProperty, value); }
}
[Description("Path the files are copied to")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string TargetDirectory
{
get { return (string)GetValue(TargetDirectoryProperty); }
set { SetValue(TargetDirectoryProperty, value); }
}
[Description("Once the the operation completes, this is set to OK or the exception information")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Optional)]
public string ActionResult
{
get { return (string)GetValue(ActionResultProperty); }
set { SetValue(ActionResultProperty, value); }
}
[Description("When the request was created")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Optional)]
public DateTime CreateDate
{
get { return (DateTime)GetValue(CreateDateProperty); }
set { SetValue(CreateDateProperty, value); }
}
[Description("When execution stoped")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Optional)]
public DateTime CompletionDate
{
get { return (DateTime)GetValue(CompletionDateProperty); }
set { SetValue(CompletionDateProperty, value); }
}
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext WFContext
{
get { return (WorkflowContext)GetValue(WFContextProperty); }
set { SetValue(WFContextProperty, value); }
}
//protected override void Initialize(IServiceProvider provider)
//{
// TraceIn();
// base.Initialize(provider);
// TraceOut();
//}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
TraceIn();
Debugger.Break();
var isSiteAdmin = false;
CreateDate = DateTime.Now;
try
{
// Needs administrative credentials to get the web application properties ino
SPSecurity.RunWithElevatedPrivileges(delegate
{
using (var site = new SPSite(WFContext.Site.ID))
{
using (var web = site.AllWebs[WFContext.Web.ID])
{
isSiteAdmin = web.CurrentUser.IsSiteAdmin;
if (string.IsNullOrEmpty(SourceDirectory)) throw new ArgumentException("SourceDirectory cannot be null or empty");
if (!Directory.Exists(SourceDirectory)) throw new DirectoryNotFoundException("Could not find source directory: \"" + SourceDirectory + "\"");
if (string.IsNullOrEmpty(TargetDirectory)) throw new ArgumentException("TargetDirectory cannot be null or empty");
if (!Directory.Exists(TargetDirectory)) throw new DirectoryNotFoundException("Could not find target directory: \"" + TargetDirectory + "\"");
// Do something
Debug.WriteLine(string.Format("Doing something amazing from {0} and moving it to {1}", SourceDirectory, TargetDirectory));
}
}
});
ActionResult = "OK";
}
catch (Exception ex)
{
ActionResult = isSiteAdmin ? ex.ToString() : ex.Message;
}
CompletionDate = DateTime.Now;
var result = base.Execute(executionContext);
TraceOut();
return result;
}
private static void TraceIn()
{
Trace("Entering");
}
private static void TraceOut()
{
Trace("Exiting");
}
private static void Trace(string direction)
{
Debugger.Launch();
var stackTrace = new StackTrace(2, false);
var frame = stackTrace.GetFrame(0);
Debug.WriteLine(direction + " " + frame.GetMethod().Name);
}
}
}
Here is the .action file.
<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
<Actions
Sequential="then"
Parallel="and">
<Action
Name="IO Activities"
ClassName="SharePoint.Activities.IO.CopyFile"
Assembly="SharePoint.Activities.IO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=abfb622251cf6982"
Category="Custom Actions"
AppliesTo="all">
<RuleDesigner
Sentence="CopyFiles from %1 to %2">
<FieldBind
Field="SourceDirectory"
DesignerType="String"
Id="1"/>
<FieldBind
Field="TargetDirectory"
DesignerType="String"
Id="2"/>
</RuleDesigner>
<Parameters>
<Parameter
Name="SourceDirectory"
Type="System.String, mscorlib"
Direction="In"
DesignerType="StringBuilder"
Description="Directory where the source files are to move" />
<Parameter
Name="TargetDirectory"
Type="System.String, mscorlib"
Direction="In"
DesignerType="StringBuilder"
Description="Directory where the files will be placed" />
<Parameter
Name="ActionResult"
Type="System.String, mscorlib"
Direction="Optional"
DesignerType="Hide" />
<Parameter
Name="CreateDate"
Type="System.DateTime, mscorlib"
Direction="Optional"
DesignerType="Hide" />
<Parameter
Name="CompletionDate"
Type="System.DateTime, mscorlib"
Direction="Optional"
DesignerType="Hide" />
<Parameter
Name="WFContext"
Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions"
DesignerType="Hide" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>
Here's a link to my post on social.msdn.microsoft.com.
Update
I've even tried removing all of the parameters by commenting out the parameters in the .actions files, the properties in the class, and commenting out the DependencyProperties. I still get the same error.
Ultimately, it looks like the issue (after I tore the project down and started over) was an incorrect designer type and a missing .
Here is the old code:
<FieldBind Field="Result" DesignerType="TextArea" Text="Action result message; returns OK on success" Id="3" />
Here is the correct code:
<FieldBind Field="Result" DesignerType="ParameterNames" Text="Action result message; returns OK on success" Id="3" />
Once that change was made, everything seems to be working.

context scope at logger level

Is there a way to set a context property value in log4net at logger level? We have scopes at thread context and global context and so on. I was wondering if there is a way to set a context variable at the logger instance level?
I know such thing does not exist but to make my point, it would be like
private static ILog _Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
_Log.LoggerContext.Properties["myVar"] = "someValue";
//now every log with this logger will use somevalue for the myVar property.
Is there a way to do such thing?
As far as I know, that capability does not exist in log4net (or NLog for that matter). I do have an idea that should work. I don't know if it is a "good" idea or not, I will leave that for you to decide...
Briefly, you could write a custom PatternLayoutConverter (see this post for one example of how to do this). This converter will look for a "context" in your own static dictionary (similar to the static dictionary contexts that log4net already has). The "context" will be stored by logger name. The value in the dictionary will be another dictionary that will hold your variables.
This a little bit more involved than I am prepared to get into right now, but I will try to give some good pseudocode to show how it might work...
UPDATE:
I have added an implementation that works (at least in the minimal testing that I have done). I have defined a "context" to hold a property bag for each logger. I have also implemented a PatternLayoutConverter to retrieve the properties for a given logger.
(The code formatting does not seem to be honoring indentation).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using log4net.Util;
using log4net.Layout.Pattern;
using log4net.Core;
namespace Log4NetTest
{
//
// Context container for loggers.
// Indexable by logger or logger name.
//
public interface IContext
{
IContextProperties this [ILog logger] { get; }
IContextProperties this [string name] { get; }
}
//
// Context properties for a specific logger.
//
public interface IContextProperties
{
object this [string key] { get; set; }
void Remove( string key );
void Clear( );
}
//
// Static class exposing the logger context container.
//
public static class LoggerProperties
{
private static readonly IContext context = new LoggerContext();
public static IContext Properties { get { return context; } }
}
internal class LoggerContext : IContext
{
private readonly IDictionary<string, IContextProperties> dict = new Dictionary<string, IContextProperties>();
#region IContext Members
//
// Get the properties asociated with this logger instance.
//
public IContextProperties this [ILog logger]
{
get
{
ILoggerWrapper w = logger as ILoggerWrapper;
ILogger i = w.Logger;
return this[i.Name];
}
}
//
// Get the properties associated with this logger name.
//
public IContextProperties this [string name]
{
get
{
lock (dict)
{
IContextProperties props;
if ( dict.TryGetValue( name, out props ) ) return props;
props = new LoggerContextProperties();
dict [name] = props;
return props;
}
}
}
#endregion
}
//
// Implementation of the logger instance properties.
//
internal class LoggerContextProperties : IContextProperties
{
private readonly IDictionary<string, object> loggerProperties = new Dictionary<string, object>();
#region IContextProperties Members
public object this [string key]
{
get
{
lock ( loggerProperties )
{
object value;
if ( loggerProperties.TryGetValue( key, out value ) ) return value;
return null;
}
}
set
{
lock ( loggerProperties )
{
loggerProperties [key] = value;
}
}
}
public void Remove( string key )
{
lock ( loggerProperties )
{
loggerProperties.Remove( key );
}
}
public void Clear( )
{
lock ( loggerProperties )
{
loggerProperties.Clear();
}
}
#endregion
}
public class LoggerContextPropertiesPatternConverter : PatternLayoutConverter
{
protected override void Convert( System.IO.TextWriter writer, LoggingEvent loggingEvent )
{
IContextProperties props = LoggerProperties.Properties[loggingEvent.LoggerName];
object value = props[Option];
if (value != null)
{
writer.Write(value);
}
else
{
writer.Write("{0}.{1} has no value", loggingEvent.LoggerName, Option);
}
}
}
}
Configure the appender to use the PatternLayoutConverter:
<appender name="debug" type="log4net.Appender.DebugAppender">
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %logger %-5p [LOGPROP = %LOGPROP{test}] %m%n"/>
<converter>
<name value="LOGPROP" />
<type value="Log4NetTest.LoggerContextPropertiesPatternConverter" />
</converter>
</layout>
</appender>
How to set the properties for a logger:
ILog loga = LogManager.GetLogger("A");
ILog logb = LogManager.GetLogger("B");
ILog logc = LogManager.GetLogger("C");
LoggerProperties.Properties[loga]["test"] = "abc";
LoggerProperties.Properties[logb]["test"] = "def";
LoggerProperties.Properties[logc]["test"] = "ghi";
loga.Debug("Hello from A");
logb.Debug("Hello from B");
logc.Debug("Hello from C");
The output:
A: 2011-07-19 10:17:07,932 [1] A DEBUG [LOGPROP = abc] Hello from A
B: 2011-07-19 10:17:07,963 [1] B DEBUG [LOGPROP = def] Hello from B
C: 2011-07-19 10:17:07,963 [1] C DEBUG [LOGPROP = ghi] Hello from C
Good luck!

Resources