diff --git a/README.md b/README.md index d17d13e..feaa0cb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ | nanoFramework.Logging.Serial (preview) | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.Logging/_apis/build/status/nanoframework.nanoFramework.Logging?branchName=develop)](https://dev.azure.com/nanoframework/nanoframework.Logging/_build/latest?definitionId=71&branchName=develop) | [![NuGet](https://img.shields.io/nuget/vpre/nanoFramework.Logging.Serial.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Logging.Serial/) | | nanoFramework.Logging.Stream | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.Logging/_apis/build/status/nanoframework.nanoFramework.Logging?branchName=main)](https://dev.azure.com/nanoframework/nanoframework.Logging/_build/latest?definitionId=71&branchName=main) | [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Logging.Stream.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Logging.Stream/) | | nanoFramework.Logging.Stream (preview) | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.Logging/_apis/build/status/nanoframework.nanoFramework.Logging?branchName=develop)](https://dev.azure.com/nanoframework/nanoframework.Logging/_build/latest?definitionId=71&branchName=develop) | [![NuGet](https://img.shields.io/nuget/vpre/nanoFramework.Logging.Stream.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Logging.Stream/) | +| nanoFramework.Logging.Syslog | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.Logging/_apis/build/status/nanoframework.nanoFramework.Logging?branchName=main)](https://dev.azure.com/nanoframework/nanoframework.Logging/_build/latest?definitionId=71&branchName=main) | [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Logging.Syslog.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Logging.Syslog/) | +| nanoFramework.Logging.Syslog (preview) | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.Logging/_apis/build/status/nanoframework.nanoFramework.Logging?branchName=develop)](https://dev.azure.com/nanoframework/nanoframework.Logging/_build/latest?definitionId=71&branchName=develop) | [![NuGet](https://img.shields.io/nuget/vpre/nanoFramework.Logging.Syslog.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Logging.Syslog/) | ## Feedback and documentation diff --git a/nanoFramework.Logging.Syslog.nuspec b/nanoFramework.Logging.Syslog.nuspec new file mode 100644 index 0000000..79c3bcc --- /dev/null +++ b/nanoFramework.Logging.Syslog.nuspec @@ -0,0 +1,42 @@ + + + + nanoFramework.Logging.Syslog + $version$ + nanoFramework.Logging.Syslog + nanoFramework project contributors + nanoFramework,dotnetfoundation + false + LICENSE.md + + + docs\README.md + false + https://github.com/nanoframework/nanoFramework.Logging + images\nf-logo.png + + Copyright (c) .NET Foundation and Contributors + This package includes the nanoFramework.Logging.Syslog assembly (Syslog Logging only) for .NET nanoFramework C# projects. +There is also a package with the Serial Logging, another with Stream Logging only and another with the basic Debug Logging. + nanoFramework C# csharp netmf netnf nanoFramework.Logging.Syslog + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nanoFramework.Logging.Syslog/Facility.cs b/nanoFramework.Logging.Syslog/Facility.cs new file mode 100644 index 0000000..6bacae4 --- /dev/null +++ b/nanoFramework.Logging.Syslog/Facility.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +namespace nanoFramework.Logging.Syslog +{ + /// + /// Available facility types (RFC 3164). + /// + public enum Facility + { + /// + /// kernel messages + /// + Kern = 0, + /// + /// user-level messages + /// + User = 1, + /// + /// mail system + /// + Mail = 2, + /// + /// system daemons + /// + Daemon = 3, + /// + /// security/authorization messages + /// + Auth = 4, + /// + /// messages generated internally by syslogd + /// + Syslog = 5, + /// + /// line printer subsystem + /// + LPR = 6, + /// + /// network news subsystem + /// + News = 7, + /// + /// UUCP subsystem + /// + UUCP = 8, + /// + /// clock daemon + /// + Cron = 9, + /// + /// security/authorization messages + /// + AuthPriv = 10, + /// + /// FTP daemon + /// + FTP = 11, + /// + /// NTP subsystem + /// + NTP = 12, + /// + /// log audit + /// + Audit = 13, + /// + /// log alert + /// + Audit2 = 14, + /// + /// clock daemon + /// + Cron2 = 15, + /// + /// local use 0 (local0) + /// + Local0 = 16, + /// + /// local use 1 (local1) + /// + Local1 = 17, + /// + /// local use 2 (local2) + /// + Local2 = 18, + /// + /// local use 3 (local3) + /// + Local3 = 19, + /// + /// local use 4 (local4) + /// + Local4 = 20, + /// + /// local use 5 (local5) + /// + Local5 = 21, + /// + /// local use 6 (local6) + /// + Local6 = 22, + /// + /// local use 7 (local7) + /// + Local7 = 23 + } +} diff --git a/nanoFramework.Logging.Syslog/Properties/AssemblyInfo.cs b/nanoFramework.Logging.Syslog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..387680d --- /dev/null +++ b/nanoFramework.Logging.Syslog/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("nanoFramework.Logging")] +[assembly: AssemblyCompany("nanoFramework Contributors")] +[assembly: AssemblyProduct("nanoFramework.Logging")] +[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +///////////////////////////////////////////////////////////////// +// This attribute is mandatory when building Interop libraries // +// update this whenever the native assembly signature changes // +[assembly: AssemblyNativeVersion("1.0.0.0")] +///////////////////////////////////////////////////////////////// diff --git a/nanoFramework.Logging.Syslog/Severity.cs b/nanoFramework.Logging.Syslog/Severity.cs new file mode 100644 index 0000000..4c715ce --- /dev/null +++ b/nanoFramework.Logging.Syslog/Severity.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using Microsoft.Extensions.Logging; + +namespace nanoFramework.Logging.Syslog +{ + /// + /// Severity represents the Severity in the Syslog messages. This corresponds to the values defined in RFC 3164 except None which means that the message won't be sent. + /// + public enum Severity + { + /// + /// Not an official RFC3164 facility. No entry will be generated when called with this Severity + /// + None = -1, + /// + /// Emergency: system is unusable. No equivalent in + /// + Emergency, + /// + /// Alert: action must be taken immediately. No equivalent in + /// + Alert, + /// + /// Critical: critical conditions. Mapped to + /// + Critical, + /// + /// Error: error conditions. Mapped to + /// + Error, + /// + /// Warning: warning conditions. Mapped to + /// + Warning, + /// + /// Notice: normal but significant condition. No equivalent in + /// + Notice, + /// + /// Informational: informational messages. Mapped to + /// + Informational, + /// + /// Debug: debug-level messages. Mapped to and + /// + Debug + }; +} diff --git a/nanoFramework.Logging.Syslog/SyslogClient.cs b/nanoFramework.Logging.Syslog/SyslogClient.cs new file mode 100644 index 0000000..1e68f2b --- /dev/null +++ b/nanoFramework.Logging.Syslog/SyslogClient.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace nanoFramework.Logging.Syslog +{ + /// + /// Syslog Client that sends logs to a Syslog server following RFC3164 format. + /// + public class SyslogClient : IDisposable + { + private readonly Socket _socket; + private bool _disposed; + + /// + /// Default Syslog UDP port : 514 + /// + public const int DefaultPort = 514; + + /// + /// Create a instance that will send RFC3164 compliant messages to a Syslog server. + /// + /// Endpoint of the server. + /// 'Hostname' part of the RFC3164 message. + /// used by this logger. + /// Local IP address to bind (if null will be used). + /// Local port to bind to (0 to choose available port). + public SyslogClient( + IPEndPoint endpoint, + string localHostname, + Facility facility = default, + IPAddress localAddress = null, + int localPort = 0) + { + Facility = facility; + LocalHostname = localHostname ?? throw new ArgumentNullException(nameof(localHostname)); + + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _socket.Bind(new IPEndPoint(localAddress ?? IPAddress.Any, localPort)); + _socket.Connect(endpoint); + } + + /// + /// Create a instance that will send RFC3164 compliant messages to a Syslog server. + /// + /// Fully Qualified Domain Name of the Syslog server. + /// Port of the Syslog server. + /// 'Hostname' part of the RFC3164 message (if null, 'nanoframework' will be used). + /// used by this logger. + /// Local IP address to bind to (if null will be used). + /// Local port to bind to (0 to choose available port). + public SyslogClient( + string hostname, + int port, + string localHostname, + Facility facility = default, + IPAddress localAddress = null, + int localPort = 0) + : this( + ResolveEndPoint(hostname, port), + localHostname, + facility, + localAddress, + localPort) + { + } + + /// + /// Facility used by this Syslog client. + /// + public Facility Facility { get; } + + /// + /// Hostname used in the 'Hostname' field in the Syslog message. + /// + public string LocalHostname { get; } + + /// + /// Send a message to a SysLog server. + /// + /// of the message. + /// Message tag (will be followed by ': ' in the message). + /// Message content. + /// + public void SendMessage( + Severity severity, + string tag, + string content) => SendMessage( + Facility, + severity, + LocalHostname, + tag, + content); + + /// + /// Send a generic Syslog message to a SysLog server. + /// + /// in the message + /// in the message + /// Hostname in the message + /// Message tag (will be followed by ': ' in the message). + /// Message content + /// + public void SendMessage( + Facility facility, + Severity severity, + string hostname, + string tag, + string content) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SyslogClient)); + } + + // We don't send message with Severity.None which isn't a RFC6134 valid value + if (severity != Severity.None) + { + _socket.Send(FormatUdpMessage(facility, severity, hostname, tag, content)); + } + } + + private static byte[] FormatUdpMessage( + Facility facility, + Severity severity, + string localHostname, + string messageTag, + string messageContent) + { + int priorityValue = ((int)facility * 8) + (int)severity; + + var headerBuilder = new StringBuilder(); + + // RFC3164 PRI + headerBuilder.Append('<').Append(priorityValue).Append('>'); + + // RFC3164 HEADER + string timestamp = DateTime.UtcNow.ToString("MMM dd HH:mm:ss"); + headerBuilder.Append(timestamp).Append(' '); + headerBuilder.Append(localHostname).Append(' '); + + // RFC3164 MSG + if (messageTag != null) + { + headerBuilder.Append(messageTag).Append(':').Append(' '); + } + + headerBuilder.Append(messageContent ?? ""); + var s = headerBuilder.ToString(); + return UTF8Encoding.UTF8.GetBytes(s); // Ideally an UTF7 is required here to be RFC3164 compliant + } + + private static IPEndPoint ResolveEndPoint( + string hostname, + int port) + { + IPHostEntry hostEntry = Dns.GetHostEntry(hostname); + if (hostEntry is null) + { + throw new ArgumentException($"Hostname `{hostname}` is not resolvable"); + } + + return new IPEndPoint(hostEntry.AddressList[0], port); + } + + /// + public void Dispose() + { + _disposed = true; + ((IDisposable)_socket)?.Dispose(); + } + } +} diff --git a/nanoFramework.Logging.Syslog/SyslogLogger.cs b/nanoFramework.Logging.Syslog/SyslogLogger.cs new file mode 100644 index 0000000..d78389c --- /dev/null +++ b/nanoFramework.Logging.Syslog/SyslogLogger.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Reflection; +using Microsoft.Extensions.Logging; + +namespace nanoFramework.Logging.Syslog +{ + /// + /// implementation that send message log to a Syslog server following RFC3164 convention. + /// While not mandatory it is recommended to use to create the SyslogLogger instances. + /// + public class SyslogLogger : ILogger + { + private readonly SyslogClient _client; + + /// + /// Create new logger based on existing . Recommended to use LoggerFactory pattern trough instead of direct use. + /// + /// to use for sending message. + /// CategoryName of this logger used as TAG in the Sys log messages. + /// + public SyslogLogger( + SyslogClient client, + string categoryName) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + LoggerCategoryName = categoryName; + } + + /// + /// Minimum log level used by this logger. + /// + public LogLevel MinLogLevel { get; set; } //by default 0 -> Trace + + /// + /// CategoryName of this logger used as TAG in the Syslog messages. + /// + public string LoggerCategoryName { get; } + + /// + /// Underlying used by this logger. + /// + public SyslogClient Client => _client; + + /// + public bool IsEnabled(LogLevel logLevel) => logLevel >= MinLogLevel; + + /// + public void Log( + LogLevel logLevel, + EventId eventId, + string state, + Exception exception, + MethodInfo format) + { + if (logLevel >= MinLogLevel && logLevel != LogLevel.None) + { + string message; + if (format is null) + { + message = exception == null ? $"{state}" : $"{state} {exception}"; + } + else + { + message = $"{(string)format.Invoke(null, new object[] { LoggerCategoryName, logLevel, eventId, state, exception })}"; + } + + _client.SendMessage(LogLevelToSeverity(logLevel), LoggerCategoryName, message); + } + } + + private Severity LogLevelToSeverity(LogLevel level) => level switch + { + LogLevel.Trace => Severity.Debug, + LogLevel.Debug => Severity.Debug, + LogLevel.Information => Severity.Informational, + LogLevel.Warning => Severity.Warning, + LogLevel.Error => Severity.Error, + LogLevel.Critical => Severity.Critical, + LogLevel.None => Severity.None, + _ => Severity.None + }; + } +} diff --git a/nanoFramework.Logging.Syslog/SyslogLoggerFactory.cs b/nanoFramework.Logging.Syslog/SyslogLoggerFactory.cs new file mode 100644 index 0000000..d424cb5 --- /dev/null +++ b/nanoFramework.Logging.Syslog/SyslogLoggerFactory.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using Microsoft.Extensions.Logging; +using System; +using System.Net; +using System.Net.Sockets; + +namespace nanoFramework.Logging.Syslog +{ + /// + /// implementation that creates instances. + /// The logger factory initializes an underlying that will be shared by all instances it will create. + /// + public class SyslogLoggerFactory : ILoggerFactory + { + const string DefaultHostname = "nanoframework"; + private readonly SyslogClient _client; + private bool _disposed; + + /// + /// Create a logging factory that will provide support for Syslog ILogger provider + /// + /// Endpoint of the server. + /// 'Hostname' part of the RFC3164 message. + /// used by this logger. + /// Local IP address to bind to (if null will be used). + /// Local port to bind to (0 to choose available port). + public SyslogLoggerFactory( + IPEndPoint endpoint, + string localHostname = DefaultHostname, + Facility facility = default, + IPAddress localAddress = null, + int localPort = 0) + { + _client = new SyslogClient(endpoint, localHostname, facility, localAddress, localPort); + } + + /// + /// Create a logging factory that will provide support for Syslog ILogger provider + /// + /// Fully Qualified Domain Name of the Syslog server. + /// Port of the Syslog server (default is 514). + /// 'Hostname' part of the RFC3164 message + /// used by this logger + /// Local IP address to bind to (if null will be used). + /// Local port to bind (0 to choose available port). + public SyslogLoggerFactory( + string hostname, + int port = SyslogClient.DefaultPort, + string localHostname = DefaultHostname, + Facility facility = default, + IPAddress localAddress = null, + int localPort = 0) + { + _client = new SyslogClient( + hostname, + port, + localHostname, + facility, + localAddress, + localPort); + } + + /// + public ILogger CreateLogger(string categoryName) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SyslogLoggerFactory)); + } + + return new SyslogLogger(_client, categoryName); + } + + /// + public void Dispose() + { + _disposed = true; + // If logger created through this factory are used after this you will get exception + _client?.Dispose(); + } + } +} diff --git a/nanoFramework.Logging.Syslog/key.snk b/nanoFramework.Logging.Syslog/key.snk new file mode 100644 index 0000000..67c9bb0 Binary files /dev/null and b/nanoFramework.Logging.Syslog/key.snk differ diff --git a/nanoFramework.Logging.Syslog/nanoFramework.Logging.Syslog.nfproj b/nanoFramework.Logging.Syslog/nanoFramework.Logging.Syslog.nfproj new file mode 100644 index 0000000..f56a396 --- /dev/null +++ b/nanoFramework.Logging.Syslog/nanoFramework.Logging.Syslog.nfproj @@ -0,0 +1,75 @@ + + + + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + a778ed84-6ea0-48c5-b97c-dc64a8d97a50 + Library + Properties + 512 + nanoFramework.Logging.Syslog + nanoFramework.Logging.Syslog + v1.0 + bin\$(Configuration)\nanoFramework.Logging.Syslog.xml + + + true + + + key.snk + + + false + + + + + + + + + + + + + ..\packages\nanoFramework.CoreLibrary.1.12.0-preview.1\lib\mscorlib.dll + + + ..\packages\nanoFramework.Runtime.Events.1.10.0-preview.4\lib\nanoFramework.Runtime.Events.dll + + + ..\packages\nanoFramework.System.Text.1.1.3-preview.7\lib\nanoFramework.System.Text.dll + + + ..\packages\nanoFramework.System.Net.1.8.0-preview.15\lib\System.Net.dll + + + ..\packages\nanoFramework.System.Threading.1.0.4-preview.8\lib\System.Threading.dll + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + \ No newline at end of file diff --git a/nanoFramework.Logging.Syslog/packages.config b/nanoFramework.Logging.Syslog/packages.config new file mode 100644 index 0000000..0fd4153 --- /dev/null +++ b/nanoFramework.Logging.Syslog/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/nanoFramework.Logging.sln b/nanoFramework.Logging.sln index 18d7f1b..6ef4703 100644 --- a/nanoFramework.Logging.sln +++ b/nanoFramework.Logging.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31105.61 @@ -13,6 +12,8 @@ Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.Logging.Seria EndProject Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.Logging.Stream", "nanoFramework.Logging.Stream\nanoFramework.Logging.Stream.nfproj", "{38F918AD-5A39-4172-B4B4-ECC2A3FA5B4E}" EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.Logging.Syslog", "nanoFramework.Logging.Syslog\nanoFramework.Logging.Syslog.nfproj", "{A778ED84-6EA0-48C5-B97C-DC64A8D97A50}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +44,12 @@ Global {38F918AD-5A39-4172-B4B4-ECC2A3FA5B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {38F918AD-5A39-4172-B4B4-ECC2A3FA5B4E}.Release|Any CPU.Build.0 = Release|Any CPU {38F918AD-5A39-4172-B4B4-ECC2A3FA5B4E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Release|Any CPU.Build.0 = Release|Any CPU + {A778ED84-6EA0-48C5-B97C-DC64A8D97A50}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE