Logging
The ShareBaseSdk features logging of HTTP requests and responses.
For convenience, metadata is stored as scope data.
The following example demonstrates how to display the data, though implementations can vary:
using Hyland.ShareBaseSdk;
using Hyland.ShareBaseSdk.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace ShareBaseSdkLoggingExample
{
/// <summary>
/// Sample program demonstrating ShareBaseSdk logging.
///
/// Dependencies:
/// - Hyland.ShareBaseSdk (tested with 0.50.0)
/// - Microsoft.Extensions.Logging (tested with 3.1.2)
/// - Microsoft.Extensions.Logging.Console (tested with 3.1.2)
/// </summary>
public class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Trace));
var consoleLogger = loggerFactory.CreateLogger<Program>();
var shareBaseLogger = new ShareBaseLogger(consoleLogger, "ShareBaseSdkLoggingExampleProgram");
using var client = new ShareBaseClient("https://app.sharebase.mx/sharebaseapi/", "Bearer TokenValueHere", "Dev Testing", "v0.0.0", shareBaseLogger);
IEnumerable<IShareBaseDocument> documents;
using (var scope = shareBaseLogger.BeginScope("GetDocuments"))
{
documents = await client.GetDocumentsInFolderAsync(726);
}
foreach (var d in documents)
{
Console.WriteLine($"{d.DocumentName}");
}
}
}
/// <summary>
/// Use with the ShareBaseSDK to get meaningful logging info.
/// </summary>
public sealed class ShareBaseLogger : ILogger
{
private readonly ILogger _logger;
private readonly LoggingContext _loggingContext;
public ShareBaseLogger(ILogger logger, string context)
{
_logger = logger;
_loggingContext = new LoggingContext(context);
}
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
string message = state.ToString();
Dictionary<string, object> properties = _loggingContext.Properties;
if (eventId != null)
{
properties["EventId"] = eventId.Name;
}
if (exception != null)
{
properties["Exception"] = exception.ToString();
}
// In this sample, we concatenate the properties into the message, and send it
// to an ILogger. In other cases, the properties could get used as column
// values.
if (properties.Count > 0)
{
StringBuilder stringBuilder = new StringBuilder(message);
foreach (var property in properties)
{
stringBuilder.Append($"\n\t**{property.Key}**: {property.Value}");
}
message = stringBuilder.ToString();
}
_logger.Log(logLevel, message);
}
public bool IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(logLevel);
}
public class NullScope : IDisposable
{
public void Dispose()
{
return;
}
}
public IDisposable BeginScope<TState>(TState state)
{
if (state == null)
{
// Return a null scope if there isn't anything to add
// because the main logging context removes scopes on
// the Dispose() call.
return new NullScope();
}
else
{
_loggingContext.AddScope(state);
}
return _loggingContext;
}
// This class keeps track of all the scopes created when BeginScope() is used
// determines the resulting Dictionary of properties that need to be logged.
private class LoggingContext : IDisposable
{
private List<object> _stack = new List<object>();
public LoggingContext() { }
public LoggingContext(object scope)
{
AddScope(scope);
}
public LoggingContext AddScope(object scope)
{
_stack.Add(scope);
return this;
}
private void RemoveLastScope()
{
_stack.RemoveAt(_stack.Count - 1);
}
public void Dispose()
{
RemoveLastScope();
}
public Dictionary<string, object> Properties
{
get
{
var properties = new Dictionary<string, object>();
var contextNameParts = new List<string>();
var additionalData = new List<object>();
foreach (var scope in _stack)
{
// If the scope is a Dictionary<string, object> we will treat them as properties of the
// log message that will each be displayed as their own property.
// These could get displayed as columns in a logging implementation
if (scope is Dictionary<string, object>)
{
// If any scopes the use the same property name, the newest value is used
foreach (var property in scope as Dictionary<string, object>)
{
properties[property.Key] = property.Value;
}
}
// If the scope is a simple string, use it as part of the Context name
// which is displayed as a "Context" value in the log output
else if (scope is string)
{
contextNameParts.Add(scope as string);
}
// If the scope is not used for context name or for properties, just serialize it and
// stash it in a generic column called "MiscData". The ShareBaseClient is currently
// only creating scopes in the above two formats, but in case that changes, we
// can catch data here instead of just omitting it from the log completely
else
{
additionalData.Add(scope);
}
}
// Join each context name part together with a period
properties["Context"] = string.Join(".", contextNameParts);
if (additionalData.Count > 0)
{
properties["MiscData"] = JsonConvert.SerializeObject(additionalData);
}
return properties;
}
}
}
}
}
An execution of this sample program would display something similar to this:
trce: ShareBaseSdkLoggingExample.Program[0]
Sending request to ShareBase API: api/folders/726/documents
**Method**: GET
**Uri**: api/folders/726/documents
**Content**:
**Context**: ShareBaseSdkLoggingExampleProgram.GetDocuments
**EventId**:
trce: ShareBaseSdkLoggingExample.Program[0]
Response received from ShareBase API: 200-OK-381
**Method**: GET
**Uri**: api/folders/726/documents
**Content**:
**ResponseCode**: 200
**ResponseMessage**: OK
**Content Length**: 381
**Context**: ShareBaseSdkLoggingExampleProgram.GetDocuments
**EventId**:
trce: ShareBaseSdkLoggingExample.Program[0]
Response: [{"DocumentId":8956,"DocumentName":"data2.bin","FolderId":726,"DateModified":"2020-06-08T20:57:58.1686628Z","Hash":"W5aoOYLU8bqDIibpNWaeziupy0s=","Links":{"Self":"https://app.sharebase.mx/sharebaseapi/api/documents/8956","Content":"https://app.sharebase.mx/sharebaseapi/api/documents/8956/content","Revisions":"https://app.sharebase.mx/sharebaseapi/api/documents/8956/revisions"}}]
**Method**: GET
**Uri**: api/folders/726/documents
**Content**:
**ResponseCode**: 200
**ResponseMessage**: OK
**Content Length**: 381
**Context**: ShareBaseSdkLoggingExampleProgram.GetDocuments
**EventId**:
data2.bin