Skip to content

1.0.0 Release #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Auto detect text files and perform LF normalization

* text=auto
51 changes: 51 additions & 0 deletions Build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
echo "build: Build started"

Push-Location $PSScriptRoot

if(Test-Path .\artifacts) {
echo "build: Cleaning .\artifacts"
Remove-Item .\artifacts -Force -Recurse
}

& dotnet restore --no-cache

$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"]

echo "build: Version suffix is $suffix"

foreach ($src in ls src/*) {
Push-Location $src

echo "build: Packaging project in $src"

& dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix
if($LASTEXITCODE -ne 0) { exit 1 }

Pop-Location
}

foreach ($test in ls test/*.PerformanceTests) {
Push-Location $test

echo "build: Building performance test project in $test"

& dotnet build -c Release
if($LASTEXITCODE -ne 0) { exit 2 }

Pop-Location
}

foreach ($test in ls test/*.Tests) {
Push-Location $test

echo "build: Testing project in $test"

& dotnet test -c Release
if($LASTEXITCODE -ne 0) { exit 3 }

Pop-Location
}

Pop-Location
171 changes: 169 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,169 @@
# serilog-extensions-logging-file
Add file logging to ASP.NET Core apps in one line of code.
# Serilog.Extensions.Logging.File [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Serilog.Extensions.Logging.File.svg)](https://nuget.org/packages/Serilog.Extensions.Logging.File) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Build status](https://ci.appveyor.com/api/projects/status/rdff6bp9oeqfxif7?svg=true)](https://ci.appveyor.com/project/serilog/serilog-extensions-logging-file)

This package makes it a one-liner - `loggerFactory.AddFile()` - to configure top-quality file logging for ASP.NET Core apps.

* Text or JSON file output
* Files roll over on date; capped file size
* Request ids and event ids included with each message
* Writes are performed on a background thread
* Files are periodically flushed to disk (required for Azure App Service log collection)
* Fast, stable, battle-proven logging code courtesy of [Serilog](https://serilog.net)

You can get started quickly with this package, and later migrate to the full Serilog API if you need more sophisticated log file configuration.

### Getting started

**1.** Add [the NuGet package](https://nuget.org/packages/serilog.extensions.logging.file) to the `"dependencies"` section of your `project.json` file:

```json
"dependencies": {
"Serilog.Extensions.Logging.File": "1.0.0"
}
```

**2.** In your `Startup` class's `Configure()` method, call `AddFile()` on the provided `loggerFactory`.

```csharp
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddFile("Logs/myapp-{Date}.txt");
```

**Done!** The framework will inject `ILogger` instances into controllers and other classes:

```csharp
class HomeController : Controller
{
readonly ILogger<HomeController> _log;

public HomeController(ILogger<HomeController> log)
{
_log = log;
}

public IActionResult Index()
{
_log.LogInformation("Hello, world!");
}
}
```

The events will appear in the log file:

```
2016-10-18T11:14:11.0881912+10:00 0HKVMUG8EMJO9 [INF] Hello, world! (f83bcf75)
```

### File format

By default, the file will be written in plain text. The fields in the log file are:

| Field | Description | Format | Example |
| ----- | ----------- | ------ | ------- |
| **Timestamp** | The time the event occurred. | ISO-8601 with offset | `2016-10-18T11:14:11.0881912+10:00` |
| **Request id** | Uniquely identifies all messages raised during a single web request. | Alphanumeric | `0HKVMUG8EMJO9` |
| **Level** | The log level assigned to the event. | Three-character code in brackets | `[INF]` |
| **Message** | The log message associated with the event. | Free text | `Hello, world!` |
| **Event id** | Identifies messages generated from the same format string/message template. | 32-bit hexadecimal, in parentheses | `(f83bcf75)` |
| **Exception** | Exception associated with the event. | `Exception.ToString()` format (not shown) | `System.DivideByZeroException: Attempt to divide by zero\r\n\ at...` |

To record events in newline-separated JSON instead, specify `isJson: true` when configuring the logger:

```csharp
loggerFactory.AddFile("Logs/myapp-{Date}.txt", isJson: true);
```

This will produce a log file with lines like:

```json
{"@t":"2016-06-07T03:44:57.8532799Z","@m":"Hello, world!","@i":"f83bcf75","RequestId":"0HKVMUG8EMJO9"}
```

The JSON document includes all properties associated with the event, not just those present in the message. This makes JSON formatted logs a better choice for offline analysis in many cases.

### Rolling

The filename provided to `AddFile()` should include the `{Date}` placeholder, which will be replaced with the date of the events contained in the file. Filenames use the `yyyyMMdd` date format so that files can be ordered using a lexicographic sort:

```
log-20160631.txt
log-20160701.txt
log-20160702.txt
```

To prevent outages due to disk space exhaustion, each file is capped to 1 GB in size. If the file size is exceeded, events will be dropped until the next roll point.

### Message templates and event ids

The provider supports the templated log messages used by _Microsoft.Extensions.Logging_. By writing events with format strings or [message templates](https://messagetemplates.org), the provider can infer which messages came from the same logging statement.

This means that although the text of two messages may be different, their **event id** fields will match, as shown by the two "view" logging statements below:

```
2016-10-18T11:14:26.2544709+10:00 0HKVMUG8EMJO9 [INF] Running view at "/Views/Home/About.cshtml". (9707eebe)
2016-10-18T11:14:11.0881912+10:00 0HKVMUG8EMJO9 [INF] Hello, world! (f83bcf75)
2016-10-18T11:14:26.2544709+10:00 0HKVMUG8EMJO9 [INF] Running view at "/Views/Home/Index.cshtml". (9707eebe)
```

Each log message describing view rendering is tagged with `(9707eebe)`, while the "hello" log message is given `(f83bcf75)`. This makes it easy to search the log for messages describing the same kind of event.

### Additional configuration

The `AddFile()` method exposes some basic options for controlling the connection and log volume.

| Parameter | Description | Example value |
| --------- | ----------- | ------------- |
| `pathFormat` | Filname to write. The filename may include `{Date}` to specify how the date portion of the filename is calculated. May include environment variables.| `Logs/log-{Date}.txt` |
| `minimumLevel` | The level below which events will be suppressed (the default is `LogLevel.Information`). | `LogLevel.Debug` |
| `levelOverrides` | A dictionary mapping logger name prefixes to minimum logging levels. | |
| `isJson` | If true, the log file will be written in JSON format. | `true` |
| `fileSizeLimitBytes` | The maximum size, in bytes, to which any single log file will be allowed to grow. For unrestricted growth, pass`null`. The default is 1 GiB. | `1024 * 1024 * 1024` |
| `retainedFileCountLimit` | The maximum number of log files that will be retained, including the current log file. For unlimited retention, pass `null`. The default is `31`. | `31` |

### `appsettings.json` configuration

The file path and other settings can be read from JSON configuration if desired.

In `appsettings.json` add a `"Logging"` property:

```json
{
"Logging": {
"PathFormat": "Logs/log-{Date}.txt",
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
}
}
}
```

And then pass the configuration section to the `AddFile()` method:

```csharp
loggerFactory.AddFile(Configuration.GetSection("Logging"));
```

In addition to the properties shown above, the `"Logging"` configuration supports:

| Property | Description | Example |
| -------- | ----------- | ------- |
| `Json` | If `true`, the log file will be written in JSON format. | `true` |
| `FileSizeLimitBytes` | The maximum size, in bytes, to which any single log file will be allowed to grow. For unrestricted growth, pass`null`. The default is 1 GiB. | `1024 * 1024 * 1024` |
| `RetainedFileCountLimit` | The maximum number of log files that will be retained, including the current log file. For unlimited retention, pass `null`. The default is `31`. | `31` |

### Using the full Serilog API

This package is opinionated, providing the most common/recommended options supported by Serilog. For more sophisticated configuration, using Serilog directly is recommened. See the instructions in [Serilog.Extensions.Logging](https://github.com/serilog/serilog-extensions-logging) to get started.

The following packages are used to provide `AddFile()`:

* [Serilog](https://github.com/serilog/serilog) - the core logging pipeline
* [Serilog.Sinks.RollingFile](https://github.com/serilog/serilog-sinks-rollingfile) - rolling file output
* [Serilog.Formatting.Compact](https://github.com/serilog/serilog-formatting-compact) - JSON event formatting
* [Serilog.Extensions.Logging](https://github.com/serilog/serilog-extensions-logging) - ASP.NET Core integration
* [Serilog.Sinks.Async](https://github.com/serilog/serilog-sinks-async) - async wrapper to perform log writes on a background thread

If you decide to switch to the full Serilog API and need help, please drop into the [Gitter channel](https://gitter.im/serilog/serilog) or post your question on [Stack Overflow](http://stackoverflow.com/questions/tagged/serilog).
29 changes: 29 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: '{build}'
skip_tags: true
image: Visual Studio 2015
configuration: Release
install:
- ps: mkdir -Force ".\build\" | Out-Null
- ps: Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain/dotnet-install.ps1" -OutFile ".\build\installcli.ps1"
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli"
- ps: '& .\build\installcli.ps1 -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath -Version 1.0.0-preview2-003121'
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
build_script:
- ps: ./Build.ps1
test: off
artifacts:
- path: artifacts/Serilog.*.nupkg
deploy:
- provider: NuGet
api_key:
secure: nvZ/z+pMS91b3kG4DgfES5AcmwwGoBYQxr9kp4XiJHj25SAlgdIxFx++1N0lFH2x
skip_symbols: true
on:
branch: /^(master|dev)$/
- provider: GitHub
auth_token:
secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
artifact: /Serilog.*\.nupkg/
tag: v$(appveyor_build_version)
on:
branch: master
Binary file added assets/Serilog.snk
Binary file not shown.
3 changes: 3 additions & 0 deletions example/WebApplication/.bowerrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"directory": "wwwroot/lib"
}
45 changes: 45 additions & 0 deletions example/WebApplication/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace WebApplication.Controllers
{
public class HomeController : Controller
{
readonly ILogger<HomeController> _log;

public HomeController(ILogger<HomeController> log)
{
_log = log;
}

public IActionResult Index()
{
_log.LogInformation("Hello, world!");

return View();
}

public IActionResult About()
{
ViewData["Message"] = "Your application description page.";

return View();
}

public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";

return View();
}

public IActionResult Error()
{
return View();
}
}
}
7 changes: 7 additions & 0 deletions example/WebApplication/Logs/another-20161018.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
2016-10-18T13:03:50.6053012+10:00 0HKVN0DHETANV [INF] Request starting HTTP/1.1 GET http://localhost:58951/ (e5be5b71)
2016-10-18T13:03:50.6057982+10:00 0HKVN0DHETAO0 [INF] Request starting HTTP/1.1 DEBUG http://localhost:58951/ 0 (e5be5b71)
2016-10-18T13:03:50.7413643+10:00 0HKVN0DHETAO0 [INF] Request finished in 148.0663ms 200 (15c52c40)
2016-10-18T13:03:51.1853627+10:00 0HKVN0DHETANV [INF] Executing action method "WebApplication.Controllers.HomeController.Index (WebApplication)" with arguments (null) - ModelState is Valid (ba7f4ac2)
2016-10-18T13:03:51.1968635+10:00 0HKVN0DHETANV [INF] Hello, world! (f83bcf75)
2016-10-18T13:04:18.3003860+10:00 0HKVN0DHETANV [INF] Executing ViewResult, running view at path "/Views/Home/Index.cshtml". (9707eebe)
2016-10-18T13:04:18.4170672+10:00 0HKVN0DHETANV [INF] User profile is available. Using '"C:\Users\nblumhardt\AppData\Local\ASP.NET\DataProtection-Keys"' as key repository and Windows DPAPI to encrypt keys at rest. (7ac5e29c)
24 changes: 24 additions & 0 deletions example/WebApplication/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

host.Run();
}
}
}
Loading