How do you create a custom EventSource in F# for Event Tracing for Windows?

回答済み How do you create a custom EventSource in F# for Event Tracing for Windows?

  • Sunday, January 20, 2013 5:12 AM
     
      Has Code

    How do you create a custom EventSource for use with ETW? While working through porting the C# Web API samples to F#, I ran into the ETW sample. I can't figure out how to solve it. Here's the C# code:

    using System.Diagnostics.Tracing;
    
    namespace WebApiEtwTracing
    {
        /// <summary>
        /// Implementation of <see cref="EventSource"/> used by <see cref="WebApiEtwTraceWriter"/>
        /// to emit ETW events.
        /// </summary>
        [EventSource(Name = "WebApi", 
                     Guid = "{fc7bbb67-1b01-557b-5e0e-c1c4e40b6a24}",
                     LocalizationResources = "WebApiEtwTracing.SRResources")]
        public class WebApiEtwEventSource : EventSource
        {
            /// <summary>
            /// Returns a singleton instance of this class.
            /// </summary>
            /// <remarks>
            /// This is the convention used by other <see cref="EventSource"/> implementations.
            /// </remarks>
            public static WebApiEtwEventSource Log = new WebApiEtwEventSource();
    
            /// <summary>
            /// Emit an ETW event for an incoming Http request.
            /// </summary>
            /// <param name="uri">The request Uri.</param>
            /// <param name="method">The request method, such as "Get".</param>
            [Event(1, 
                    Level = EventLevel.Informational, 
                    Opcode = EventOpcode.Receive, 
                    Keywords = Keywords.Diagnostic,
                    Task = Tasks.Request)]
            public void Request(string uri, string method)
            {
                WriteEvent(1, uri, method);
            }
    
            //
            // More of the same ...
            //
    
            /// <summary>
            /// Simple enum wrapper for the possible keyword values
            /// attached to the ETW events.
            /// </summary>
            public class Keywords
            {
                /// <summary>
                /// Identifies an ETW trace as diagnostic information.
                /// </summary>
                public const EventKeywords Diagnostic = (EventKeywords)1;
            }
    
            /// <summary>
            /// Simple enum wrapper for possible Task values
            /// </summary>
            public class Tasks
            {
                public const EventTask Request =  (EventTask)1;
                public const EventTask Response = (EventTask)2;
                public const EventTask OpBegin =  (EventTask)3;
                public const EventTask OpEnd =    (EventTask)4;
                public const EventTask OpTrace =  (EventTask)5;
                public const EventTask Warning =  (EventTask)6;
                public const EventTask Error =    (EventTask)7;
            }
        }
    }
    

    Specifically, the public const fields and the casts to EventKeywords and EventTask don't seem to have a direct conversion in F#. Here's my attempt:

    namespace WebApiEtwTracing
    
    open System.Diagnostics.Tracing
    
    /// Simple enum wrapper for possible Task values
    module Tasks =
        [<Literal>]
        let Request : EventTask = LanguagePrimitives.EnumOfValue 1
        [<Literal>]
        let Response : EventTask = LanguagePrimitives.EnumOfValue 2
        [<Literal>]
        let OpBegin : EventTask = LanguagePrimitives.EnumOfValue 3
        [<Literal>]
        let OpEnd : EventTask = LanguagePrimitives.EnumOfValue 4
        [<Literal>]
        let OpTrace : EventTask = LanguagePrimitives.EnumOfValue 5
        [<Literal>]
        let Warning : EventTask = LanguagePrimitives.EnumOfValue 6
        [<Literal>]
        let Error : EventTask = LanguagePrimitives.EnumOfValue 7
    
    /// Simple enum wrapper for the possible keyword values
    /// attached to the ETW events.
    module Keywords =
        /// Identifies an ETW trace as diagnostic information.
        [<Literal>]
        let Diagnostic : EventKeywords = LanguagePrimitives.EnumOfValue 1
    
    /// Implementation of <see cref="EventSource"/> used by <see cref="WebApiEtwTraceWriter"/>
    /// to emit ETW events.
    [<EventSource(Name = "WebApi", 
                  Guid = "{fc7bbb67-1b01-557b-5e0e-c1c4e40b6a24}",
                  LocalizationResources = "WebApiEtwTracing.SRResources")>]
    type WebApiEtwEventSource() as x =
        inherit EventSource()
    
        static let mutable log = new WebApiEtwEventSource()
        /// <summary>
        /// Returns a singleton instance of this class.
        /// </summary>
        /// <remarks>
        /// This is the convention used by other <see cref="EventSource"/> implementations.
        /// </remarks>
        static member Log
            with get() = log
            and set(v) = log <- v
    
        /// <summary>
        /// Emit an ETW event for an incoming Http request.
        /// </summary>
        /// <param name="uri">The request Uri.</param>
        /// <param name="method">The request method, such as "Get".</param>
        [<Event(1, 
                Level = EventLevel.Informational, 
                Opcode = EventOpcode.Receive, 
                Keywords = Keywords.Diagnostic,
                Task = Tasks.Request)>]
        member x.Request(uri: string, methd: string) =
            x.WriteEvent(1, uri, methd)
    
        //
        // More of the same ...
        //

    The calls to EnumOfValue are not able to create literals, so those fail, and thus so do their uses in the attributes.

    Would this be a candidate for a type provider?

    Thanks,

    Ryan


    Ryan Riley

All Replies

  • Thursday, April 04, 2013 2:11 AM
     
      Has Code

    Thanks to a bit of help from an answer on the http://fpish.net/ site, I was able to get part of the issue resolved. However, I still cannot correctly cast EventKeywords. The compiler tells me that the value is not a constant expression. Here's the latest:

    namespace WebApiEtwTracing
    
    open System.Diagnostics.Tracing
    open Microsoft.FSharp.Core
    
    /// Simple enum wrapper for possible Task values
    module Tasks =
        let [<Literal>] Request = 1
        let [<Literal>] Response = 2
        let [<Literal>] OpBegin = 3
        let [<Literal>] OpEnd = 4
        let [<Literal>] OpTrace = 5
        let [<Literal>] Warning = 6
        let [<Literal>] Error = 7
    
    /// Simple enum wrapper for the possible keyword values
    /// attached to the ETW events.
    module Keywords =
        /// Identifies an ETW trace as diagnostic information.
        let [<Literal>] Diagnostic = 1L
    
    /// Implementation of <see cref="EventSource"/> used by <see cref="WebApiEtwTraceWriter"/>
    /// to emit ETW events.
    [<EventSource(Name = "WebApi", 
                  Guid = "{fc7bbb67-1b01-557b-5e0e-c1c4e40b6a24}",
                  LocalizationResources = "WebApiEtwTracing.SRResources")>]
    type WebApiEtwEventSource() as x =
        inherit EventSource()
    
        static let mutable log = new WebApiEtwEventSource()
        /// <summary>
        /// Returns a singleton instance of this class.
        /// </summary>
        /// <remarks>
        /// This is the convention used by other <see cref="EventSource"/> implementations.
        /// </remarks>
        static member Log
            with get() = log
            and set(v) = log <- v
    
        /// <summary>
        /// Emit an ETW event for an incoming Http request.
        /// </summary>
        /// <param name="uri">The request Uri.</param>
        /// <param name="method">The request method, such as "Get".</param>
        [<Event(1, 
                Level = EventLevel.Informational, 
                Opcode = EventOpcode.Receive, 
                Keywords = enum64<EventKeywords> Keywords.Diagnostic,
                Task = enum<EventTask> Tasks.Request)>]
        member x.Request(uri: string, methd: string) =
            x.WriteEvent(1, uri, methd)
    
        // More events ...

    I've also defined the following helper, though neither this helper nor the direct use of EnumOfValue help:

    namespace Microsoft.FSharp.Core
    
    [<AutoOpen>]
    module Operators =
        [<CompiledName("ToEnumFromInt64")>]
        let inline enum64< ^T when ^T : enum<int64> > (x:int64) : ^T = LanguagePrimitives.EnumOfValue<int64, ^T> x 
    

    Any ideas?


    Ryan Riley


  • Monday, April 08, 2013 4:20 PM
    Moderator
     
     
    Sadly, it is not possible to use EnumOfValue (or custom equivalents) to define literals or attribute arguments in the current version of F#, so I think you're out of luck for enums backed by anything other than int.
  • Monday, April 08, 2013 4:23 PM
     
     
    Sadly, it is not possible to use EnumOfValue (or custom equivalents) to define literals or attribute arguments in the current version of F#, so I think you're out of luck for enums backed by anything other than int.
    Do you have any suggestions for working around this issue in order to work with ETW? Is there another way to use ETW than with attributes as above? I'll ask the same in the ETW forum.

    Ryan Riley

  • Monday, April 08, 2013 5:13 PM
    Moderator
     
     Answered

    I can't think of an F#-only solution (unless you're using one of the predefined EventKeywords enum values, which should work fine).  It's not a very satisfactory option, but you could put the new EventKeywords constants you need in a C# DLL and reference that from your F# code.


  • Monday, April 08, 2013 6:11 PM
     
     
    Do you happen to know who I should contact on the ETW side? This doesn't appear to be a bug, but it's certainly an annoying short-coming in the API design.

    Ryan Riley

  • Monday, April 08, 2013 7:21 PM
    Moderator
     
     
    Sorry, I don't know anything about the ETW team.  But really, this is an F# problem - as a basic matter of .NET interop, you should be able to specify enum constants with non-int backing types.  I've passed this feedback on to the F# team.