/// <summary>
        /// Get logs.
        /// </summary>
        /// <param name="resourceUri">The resourceUri</param>
        /// <param name="filterString">The filter string</param>
        /// <param name="definitions">The log definitions</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns></returns>
        public async Task<LogListResponse> GetLogsAsync(
            string resourceUri, 
            string filterString, 
            IEnumerable<LogDefinition> definitions, 
            CancellationToken cancellationToken)
        {
            if (definitions == null)
            {
                throw new ArgumentNullException("definitions");
            }

            if (resourceUri == null)
            {
                throw new ArgumentNullException("resourceUri");
            }

            // Ensure exactly one '/' at the start
            resourceUri = '/' + resourceUri.TrimStart('/');

            LogListResponse result;
            string invocationId = TracingAdapter.NextInvocationId.ToString(CultureInfo.InvariantCulture);

            // If no definitions provided, return empty collection
            if (!definitions.Any())
            {
                this.LogStartGetLogs(invocationId, resourceUri, filterString, definitions);
                result = new LogListResponse()
                {
                    RequestId = Guid.NewGuid().ToString("D"),
                    StatusCode = HttpStatusCode.OK,
                    LogCollection = new LogCollection()
                    {
                        Value = new Log[0]
                    }
                };

                this.LogEndGetLogs(invocationId, result);

                return result;
            }

            // Parse LogFilter. Reusing the metric filter. 
            // We might consider extracting the parsing functionality to a class with a less specific name
            MetricFilter filter = MetricFilterExpressionParser.Parse(filterString, false);

            if (filter.StartTime == default(DateTime)) 
            {
                throw new InvalidOperationException("startTime is required");
            }

            if (filter.EndTime == default(DateTime))
            {
                throw new InvalidOperationException("endTime is required");
            }

            this.LogStartGetLogs(invocationId, resourceUri, filterString, definitions);

            var logsPerBlob = new Dictionary<string, Task<Log>>(StringComparer.OrdinalIgnoreCase);

            // We download all the relevant blobs first and then use the data later, to avoid download the same blob more than once.
            foreach (LogDefinition logDefinition in definitions)
            {
                foreach (BlobInfo blobInfo in logDefinition.BlobLocation.BlobInfo)
                {
                    if (blobInfo.EndTime < filter.StartTime || blobInfo.StartTime >= filter.EndTime)
                    {
                        continue;
                    }

                    string blobId = GetBlobEndpoint(blobInfo);
                    if (!logsPerBlob.ContainsKey(blobId))
                    {
                        logsPerBlob.Add(blobId, FetchLogFromBlob(blobInfo.BlobUri, filter, logDefinition.Category));
                    }
                }
            }

            foreach (var task in logsPerBlob.Values)
            {
                await task;
            }

            var logsPerCategory = new Dictionary<string, Log>();
            foreach (var task in logsPerBlob.Values)
            {
                Log log = task.Result;
                Log existingLog;
                if (logsPerCategory.TryGetValue(log.Category.Value, out existingLog))
                {
                    ((List<LogValue>)existingLog.Value).AddRange(log.Value);
                    existingLog.StartTime = this.Min(log.StartTime, existingLog.StartTime);
                    existingLog.EndTime = this.Max(log.StartTime, existingLog.StartTime);
                }
                else
                {
                    logsPerCategory.Add(log.Category.Value, log);
                }
            }

            result = new LogListResponse
            {
                RequestId = Guid.NewGuid().ToString("D"),
                StatusCode = HttpStatusCode.OK,
                LogCollection = new LogCollection
                {
                    Value = logsPerCategory.Values.ToList()
                }
            };

            this.LogEndGetLogs(invocationId, result);

            return result;
        }
 private void LogEndGetLogs(string invocationId, LogListResponse result)
 {
     if (TracingAdapter.IsEnabled)
     {
         TracingAdapter.Exit(invocationId, result);
     }
 }