public void Trace(TraceLevel level, string message, Exception ex)
    {
      // write to any existing trace writer
      if (_innerTraceWriter != null && level <= _innerTraceWriter.LevelFilter)
        _innerTraceWriter.Trace(level, message, ex);

      IExecutionTimer timer = _timerStrategy();
      
      if (_traceMessages.Count > 0)
      {
        // check message to see if serialization is complete
        if (message.StartsWith("Serialized JSON:", StringComparison.Ordinal) || message.StartsWith("Deserialized JSON:", StringComparison.Ordinal))
        {
          TimerResult timeResult = null;
          if (timer != null)
          {
            timeResult = timer.Stop(_start);
            _timelineMessage.AsTimedMessage(timeResult);
          }

          // set final JSON onto previous message
          JsonTraceMessage lastMessage = _traceMessages.Last();
          lastMessage.JsonText = message.Substring(message.IndexOf(Environment.NewLine, StringComparison.Ordinal)).Trim();
          lastMessage.Duration = (timeResult != null) ? (TimeSpan?) timeResult.Duration : null;

          _traceMessages.Clear();
          return;
        }
      }

      JsonAction action = JsonAction.Unknown;
      string type = null;
      string json = null;
      if (_traceMessages.Count == 0)
      {
        Match match = Regex.Match(message, @"^Started serializing ([^\s]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
        if (match.Success)
        {
          type = match.Groups[1].Value.TrimEnd('.');
          action = JsonAction.Serialize;
        }
        else
        {
          match = Regex.Match(message, @"^Started deserializing ([^\s]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
          if (match.Success)
          {
            type = match.Groups[1].Value.TrimEnd('.');
            action = JsonAction.Deserialize;
          }
          else
          {
            if (message.StartsWith("Serialized JSON:", StringComparison.Ordinal))
              action = JsonAction.Serialize;
            else if (message.StartsWith("Deserialized JSON:", StringComparison.Ordinal))
              action = JsonAction.Deserialize;

            if (action != JsonAction.Unknown)
            {
              json = message.Substring(message.IndexOf(Environment.NewLine, StringComparison.Ordinal)).Trim();
              message = null;
            }
          }
        }

        // create timeline message
        // will be updated each trace with new duration
        _timelineMessage = CreateJsonTimelineMessage(action, type);
        _messageBroker.Publish(_timelineMessage);

        if (timer != null)
          _start = timer.Start();
      }
      else
      {
        JsonTraceMessage previous = _traceMessages.Last();
        previous.Duration = null;

        action = previous.Action;
        type = previous.Type;
      }

      TimerResult result = null;
      if (timer != null)
      {
        result = timer.Stop(_start);
        _timelineMessage.AsTimedMessage(result);
      }

      JsonTraceMessage traceMessage = new JsonTraceMessage
        {
          Ordinal = _traceMessages.Count,
          MessageDate = DateTime.Now,
          Level = level,
          Message = message,
          Exception = ex,
          JsonText = json,
          Action = action,
          Type = (type != null) ? RemoveAssemblyDetails(type) : null,
          Duration = (result != null) ? (TimeSpan?)result.Duration : null
        };

      _messageBroker.Publish(traceMessage);
      _traceMessages.Add(traceMessage);
    }
 private static JsonTimelineMessage CreateJsonTimelineMessage(JsonAction action, string type)
 {
   JsonTimelineMessage timelineMessage = new JsonTimelineMessage();
   string eventName = action.ToString("G");
   if (type != null)
     eventName += " - " + RemoveAssemblyDetails(type);
   timelineMessage.AsTimelineMessage(eventName, new TimelineCategoryItem(action.ToString("G"), "#B3DF00", "#9BBB59"));
   return timelineMessage;
 }