diff --git a/.editorconfig b/.editorconfig index c6ee8061..1d9dae68 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,319 @@ -[*.cs] +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference + +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +indent_style = space +tab_width = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true +max_line_length = 120 + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# YAML files +[*.{yml,yaml}] +indent_size = 2 + +############################### +# .NET Coding Conventions # +############################### + +#### C# Formatting Rules #### +[*.cs] + +# Null-checking preferences +csharp_style_throw_expression = true : suggestion +csharp_style_conditional_delegate_call = true : suggestion + +# Modifier preferences +# require braces to be on a new line for control_blocks, types, and methods (also known as "Allman" style) +csharp_new_line_before_open_brace = control_blocks, types, methods, properties, accessors, object_collection +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async : suggestion +csharp_new_line_before_catch = true +csharp_new_line_before_else = true : suggestion +csharp_new_line_before_finally = true : suggestion +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +#Formatting - organize using options + +dotnet_sort_system_directives_first = false +dotnet_separate_import_directive_groups = false +csharp_prefer_simple_using_statement = true:warning +csharp_using_directive_placement = inside_namespace:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent + +#Formatting - spacing options + +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Pattern - matching preferences +csharp_style_pattern_local_over_anonymous_function = true : suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion +csharp_style_pattern_matching_over_as_with_null_check = true : suggestion + +#Style - expression bodied member options +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:silent + +#Style - expression level options +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true : suggestion +csharp_prefer_simple_default_expression = true : suggestion +csharp_style_inlined_variable_declaration = false : suggestion + +#Style - implicit and explicit types +csharp_style_var_for_built_in_types = false : suggestion +csharp_style_var_when_type_is_apparent = true : suggestion +csharp_style_var_elsewhere = false : suggestion + +#Style - preferences +csharp_style_prefer_index_operator = true : warning +csharp_style_prefer_not_pattern = true : warning +csharp_style_prefer_pattern_matching = true : suggestion +csharp_style_prefer_switch_expression = true : suggestion +csharp_style_prefer_range_operator = false : warning + +#Style - unused values +csharp_style_unused_value_assignment_preference = discard_variable : suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable : suggestion + +#prefer the language keyword for local variables, method parameters, and class members, +#instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion +dotnet_style_predefined_type_for_member_access = true : suggestion + +#Style - qualification options +dotnet_style_qualification_for_event = false : suggestion +dotnet_style_qualification_for_field = false : suggestion +dotnet_style_qualification_for_method = false : suggestion +dotnet_style_qualification_for_property = false : suggestion + +#Style - Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity : silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity : silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity : silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary : silent + +#Style - Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members : silent +dotnet_style_readonly_field = true : suggestion + +#Style - Expression-level preferences +dotnet_style_object_initializer = true : suggestion +dotnet_style_collection_initializer = true : suggestion +dotnet_style_explicit_tuple_names = true : suggestion +dotnet_style_null_propagation = true : suggestion +dotnet_style_coalesce_expression = true : suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true : silent +dotnet_style_prefer_inferred_tuple_names = true : suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true : suggestion +dotnet_style_prefer_auto_properties = true : silent +dotnet_style_prefer_conditional_expression_over_assignment = true : suggestion +dotnet_style_prefer_conditional_expression_over_return = true : suggestion +dotnet_style_prefer_compound_assignment = true : silent +dotnet_style_prefer_simplified_boolean_expressions = true : silent +dotnet_style_prefer_simplified_interpolation = true : silent + +# Naming rules + +# Async methods should have "Async" suffix +dotnet_naming_rule.async_methods_end_in_async.severity = suggestion +dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods +dotnet_naming_rule.async_methods_end_in_async.style = end_in_async + +dotnet_naming_symbols.any_async_methods.applicable_kinds = method, interface +dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * +dotnet_naming_symbols.any_async_methods.required_modifiers = async + +dotnet_naming_style.end_in_async.required_suffix = Async +dotnet_naming_style.end_in_async.capitalization = pascal_case + +# Private Constants are PascalCase and start with k +dotnet_naming_rule.constants_private_should_start_with_k.severity = suggestion +dotnet_naming_rule.constants_private_should_start_with_k.symbols = constants_with_k +dotnet_naming_rule.constants_private_should_start_with_k.style = constants_with_k_style + +dotnet_naming_symbols.constants_with_k.applicable_kinds = field, local +dotnet_naming_symbols.constants_with_k.applicable_accessibilities = private, protected, private_protected, protected_friend +dotnet_naming_symbols.constants_with_k.required_modifiers = const + +dotnet_naming_style.constants_with_k_style.capitalization = pascal_case +dotnet_naming_style.constants_with_k_style.required_prefix = k + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.applicable_accessibilities = public, internal, protected_internal +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Non-private readonly fields are PascalCase +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly + +dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Private instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field +dotnet_naming_symbols.instance_fields.applicable_accessibilities = private, protected, private_protected, protected_friend + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Diagnostic settings (windows only) +dotnet_analyzer_diagnostic.category-reliability.severity = warning +dotnet_analyzer_diagnostic.category-performance.severity = warning +dotnet_analyzer_diagnostic.category-security.severity = warning + +# CA1805: Do not initialize unnecessarily. +dotnet_diagnostic.CA1805.severity = suggestion +dotnet_code_quality.CA1805.api_surface = private, internal + +# CA1822: Mark members as static. +dotnet_diagnostic.CA1822.severity = suggestion +dotnet_code_quality.CA1822.api_surface = private, internal + +# CA3075: Insecure DTD processing in XML +dotnet_diagnostic.CA3075.severity = warning + +# CA2007: Do not directly await a Task +dotnet_diagnostic.CA2007.severity = warning # RCS1163: Unused parameter. -dotnet_diagnostic.RCS1163.severity = none +dotnet_diagnostic.RCS1163.severity = none # RCS1141: Add 'param' element to documentation comment. -dotnet_diagnostic.RCS1141.severity = none +dotnet_diagnostic.RCS1141.severity = none + +# CA1848: Use Logger message delegates +dotnet_diagnostic.CA1848.severity = none + +# CA1852: Seal internal Types +dotnet_diagnostic.CA1852.severity = none + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = suggestion + +# CA1861: Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array +dotnet_diagnostic.CA1861.severity = suggestion + diff --git a/src/AlarmCondition/Model/AreaState.cs b/src/AlarmCondition/Model/AreaState.cs index a1b923a5..882ddcf8 100644 --- a/src/AlarmCondition/Model/AreaState.cs +++ b/src/AlarmCondition/Model/AreaState.cs @@ -27,8 +27,6 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System; -using System.Collections.Generic; using Opc.Ua; namespace AlarmCondition diff --git a/src/AlarmCondition/Model/ModelUtils.cs b/src/AlarmCondition/Model/ModelUtils.cs index 092f5d4c..5a11cfdb 100644 --- a/src/AlarmCondition/Model/ModelUtils.cs +++ b/src/AlarmCondition/Model/ModelUtils.cs @@ -27,11 +27,8 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System; -using System.Collections.Generic; using System.Text; using Opc.Ua; -using Opc.Ua.Server; namespace AlarmCondition { diff --git a/src/AlarmCondition/Model/Namespaces.cs b/src/AlarmCondition/Model/Namespaces.cs index c5df659c..f6baf0e3 100644 --- a/src/AlarmCondition/Model/Namespaces.cs +++ b/src/AlarmCondition/Model/Namespaces.cs @@ -27,9 +27,6 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System; -using System.Collections.Generic; - namespace AlarmCondition { /// diff --git a/src/AlarmCondition/NodeHandle.cs b/src/AlarmCondition/NodeHandle.cs index 27834ab6..c4dcc2fc 100644 --- a/src/AlarmCondition/NodeHandle.cs +++ b/src/AlarmCondition/NodeHandle.cs @@ -27,10 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System; -using System.Text; using Opc.Ua; -using Opc.Ua.Server; namespace AlarmCondition { diff --git a/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarm.cs b/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarm.cs index d16a3504..acb68876 100644 --- a/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarm.cs +++ b/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarm.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Generic; using Opc.Ua; namespace AlarmCondition diff --git a/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarmStates.cs b/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarmStates.cs index 148ddd26..a04dff85 100644 --- a/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarmStates.cs +++ b/src/AlarmCondition/UnderlyingSystem/UnderlyingSystemAlarmStates.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Generic; namespace AlarmCondition { diff --git a/src/CliOptions.cs b/src/CliOptions.cs index db626733..15252609 100644 --- a/src/CliOptions.cs +++ b/src/CliOptions.cs @@ -1,5 +1,6 @@ -namespace OpcPlc; +namespace OpcPlc; +using Microsoft.Extensions.Logging; using Mono.Options; using Opc.Ua; using OpcPlc.Helpers; @@ -31,11 +32,11 @@ public static Mono.Options.OptionSet InitCommandLineOptions() } } }, - { "ll|loglevel=", "the loglevel to use (allowed: fatal, error, warn, info, debug, verbose).\nDefault: info", (string s) => { - var logLevels = new List {"fatal", "error", "warn", "info", "debug", "verbose"}; + { "ll|loglevel=", "the loglevel to use (allowed: critical, warn, info, debug, trace).\nDefault: info", (string s) => { + var logLevels = new List {"critical", "error", "warn", "info", "debug", "trace"}; if (logLevels.Contains(s.ToLowerInvariant())) { - Program.LogLevel = s.ToLowerInvariant(); + Program.LogLevelCli = s.ToLowerInvariant(); } else { @@ -232,7 +233,7 @@ public static void PrintUsage(Mono.Options.OptionSet options) using var stringWriter = new StringWriter(sb); options.WriteOptionDescriptions(stringWriter); - Program.Logger.Information(sb.ToString()); + Program.Logger.LogInformation(sb.ToString()); } /// @@ -256,7 +257,7 @@ private static List ParseListOfStrings(string list) else if (list.Contains(',')) { strings = list.Split(',').ToList(); - strings.ForEach(st => st.Trim()); + strings.ForEach(st => st = st.Trim()); strings = strings.Select(st => st.Trim()).ToList(); } else diff --git a/src/DeterministicAlarms/Configuration/Configuration.cs b/src/DeterministicAlarms/Configuration/Configuration.cs index b0b49ac1..e7890a37 100644 --- a/src/DeterministicAlarms/Configuration/Configuration.cs +++ b/src/DeterministicAlarms/Configuration/Configuration.cs @@ -1,41 +1,41 @@ -namespace OpcPlc.DeterministicAlarms.Configuration; +namespace OpcPlc.DeterministicAlarms.Configuration; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; public class Configuration -{ +{ + static readonly JsonSerializerOptions _fromJsonOptions = new JsonSerializerOptions { + ReadCommentHandling = JsonCommentHandling.Skip, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(), + }, + }; + + static readonly JsonSerializerOptions _toJsonOptions = new JsonSerializerOptions { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter() + } + }; + public List Folders { get; set; } public Script Script { get; set; } public string ToJson() { - return JsonSerializer.Serialize(this, - new JsonSerializerOptions - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter() - } - }); + return JsonSerializer.Serialize(this, _toJsonOptions); } public static Configuration FromJson(string json) { - return JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - ReadCommentHandling = JsonCommentHandling.Skip, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(), - }, - }); + return JsonSerializer.Deserialize(json, _fromJsonOptions); } } diff --git a/src/DeterministicAlarms/DeterministicAlarmsNodeManager.cs b/src/DeterministicAlarms/DeterministicAlarmsNodeManager.cs index 8b34241f..91e9a668 100644 --- a/src/DeterministicAlarms/DeterministicAlarmsNodeManager.cs +++ b/src/DeterministicAlarms/DeterministicAlarmsNodeManager.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using static OpcPlc.Program; +using Microsoft.Extensions.Logging; public class DeterministicAlarmsNodeManager : CustomNodeManager2 { @@ -49,7 +50,7 @@ public DeterministicAlarmsNodeManager(IServerInternal server, ApplicationConfigu } catch (Exception ex) { - Logger.Error(ex, "Cannot read or decode deterministic alarm script file"); + Logger.LogError(ex, "Cannot read or decode deterministic alarm script file"); } } @@ -115,12 +116,12 @@ private void ReplayScriptStart(Configuration.Configuration scriptConfiguration) try { VerifyScriptConfiguration(scriptConfiguration); - Logger.Information("Script starts executing"); + Logger.LogInformation("Script starts executing"); var scriptEngine = new ScriptEngine(scriptConfiguration.Script, OnScriptStepAvailable, _timeService); } catch (ScriptException ex) { - Logger.Error($"Script Engine Exception '{ex.Message}'\nSCRIPT WILL NOT START"); + Logger.LogError($"Script Engine Exception '{ex.Message}'\nSCRIPT WILL NOT START"); throw; } } @@ -134,7 +135,7 @@ private void OnScriptStepAvailable(Step step, long loopNumber) { if (step == null) { - Logger.Information("SCRIPT ENDED"); + Logger.LogInformation("SCRIPT ENDED"); } else { @@ -199,16 +200,16 @@ private void PrintScriptStep(Step step, long loopNumber) { if (step.Event != null) { - Logger.Information($"({loopNumber}) -\t{step.Event.AlarmId}\t{step.Event.Reason}"); + Logger.LogInformation($"({loopNumber}) -\t{step.Event.AlarmId}\t{step.Event.Reason}"); foreach (var sc in step.Event.StateChanges) { - Logger.Information($"\t\t{sc.StateType} - {sc.State}"); + Logger.LogInformation($"\t\t{sc.StateType} - {sc.State}"); } } if (step.SleepInSeconds > 0) { - Logger.Information($"({loopNumber}) -\tSleep: {step.SleepInSeconds}"); + Logger.LogInformation($"({loopNumber}) -\tSleep: {step.SleepInSeconds}"); } } diff --git a/src/Helpers/PnJsonHelper.cs b/src/Helpers/PnJsonHelper.cs index 5645f2bb..080931cd 100644 --- a/src/Helpers/PnJsonHelper.cs +++ b/src/Helpers/PnJsonHelper.cs @@ -1,7 +1,7 @@ namespace OpcPlc.Helpers; +using Microsoft.Extensions.Logging; using OpcPlc.PluginNodes.Models; -using Serilog; using System; using System.Collections.Immutable; using System.IO; @@ -53,7 +53,7 @@ public static async Task PrintPublisherConfigJsonAsync(string pnJsonFileName, st sb.AppendLine("]"); string pnJson = sb.ToString(); - logger.Information("OPC Publisher configuration file: {pnJsonFile}", $"{pnJsonFileName}{pnJson}"); + logger.LogInformation("OPC Publisher configuration file: {pnJsonFile}", $"{pnJsonFileName}{pnJson}"); await File.WriteAllTextAsync(pnJsonFileName, pnJson.Trim()).ConfigureAwait(false); } diff --git a/src/Logging/LoggingProvider.cs b/src/Logging/LoggingProvider.cs new file mode 100644 index 00000000..b9c910f5 --- /dev/null +++ b/src/Logging/LoggingProvider.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace OpcPlc.Logging; + +using Microsoft.Extensions.Logging; +using System; + +/// +/// Provides utility for creating logger factory. +/// +public static class LoggingProvider +{ + /// + /// Create ILoggerFactory object with default configuration. + /// + public static ILoggerFactory CreateDefaultLoggerFactory(LogLevel level) + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(options => options.FormatterName = nameof(SyslogFormatter)) + .SetMinimumLevel(level) + .AddConsoleFormatter< + SyslogFormatter, + SyslogFormatterOptions>(); + }); + + return loggerFactory; + } +} diff --git a/src/Logging/SyslogFormatter.cs b/src/Logging/SyslogFormatter.cs new file mode 100644 index 00000000..b08efa66 --- /dev/null +++ b/src/Logging/SyslogFormatter.cs @@ -0,0 +1,134 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace OpcPlc.Logging; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Logging.Console; +using Microsoft.Extensions.Options; +using System; +using System.Globalization; +using System.IO; +using System.Text; + +/// +/// Logging formatter compatible with syslogs format. +/// +public sealed class SyslogFormatter : ConsoleFormatter, IDisposable +{ + private const int _initialLength = 256; + + /// + /// Map of to syslog severity. + /// + private static readonly string[] _syslogMap = new string[] { + /* Trace */ "<7>", + /* Debug */ "<7>", + /* Info */ "<6>", + /* Warn */ "<4>", + /* Error */ "<3>", + /* Crit */ "<3>", + }; + + private readonly IDisposable _optionsReloadToken; + + private string _timestampFormat; + private string _serviceId; + private bool _includeScopes; + + private SyslogFormatterOptions _options; + + /// + /// Initializes a new instance of the class. + /// + public SyslogFormatter(SyslogFormatterOptions options) + : base(nameof(SyslogFormatter)) + { + _optionsReloadToken = null; + _serviceId = options.ServiceId; + _timestampFormat = options.TimestampFormat ?? SyslogFormatterOptions.DefaultTimestampFormat; + _includeScopes = options.IncludeScopes; + } + + /// + /// Initializes a new instance of the class. + /// + public SyslogFormatter(IOptionsMonitor options) + : base(nameof(SyslogFormatter)) + { + _optionsReloadToken = options.OnChange(opt => + { + _options = opt; + _serviceId = opt.ServiceId; + _timestampFormat = opt.TimestampFormat ?? SyslogFormatterOptions.DefaultTimestampFormat; + _includeScopes = opt.IncludeScopes; + }); + _options = options.CurrentValue; + _serviceId = _options.ServiceId; + _timestampFormat = _options.TimestampFormat ?? SyslogFormatterOptions.DefaultTimestampFormat; + _includeScopes = _options.IncludeScopes; + } + + /// + public override void Write( + in LogEntry logEntry, + IExternalScopeProvider scopeProvider, + TextWriter textWriter) + { + string message = + logEntry.Formatter?.Invoke( + logEntry.State, logEntry.Exception); + + if (message is null) + { + return; + } + + var messageBuilder = new StringBuilder(_initialLength); + messageBuilder.Append(_syslogMap[(int)logEntry.LogLevel]); + messageBuilder.Append(DateTime.UtcNow.ToString(_timestampFormat, CultureInfo.InvariantCulture)); + + if (_includeScopes && scopeProvider != null && !string.IsNullOrEmpty(_serviceId)) + { + bool structuredData = false; + scopeProvider.ForEachScope( + (scope, state) => + { + StringBuilder builder = state; + if (!structuredData) + { + messageBuilder.Append('['); + messageBuilder.Append(_serviceId); + structuredData = true; + } + + builder.Append(' ').Append(scope); + }, + messageBuilder); + if (structuredData) + { + messageBuilder.Append("] "); + } + } + + messageBuilder.Append("- "); + messageBuilder.AppendLine(message); + + if (logEntry.Exception != null) + { + // TODO: syslog format does not support stack traces + messageBuilder.AppendLine(logEntry.Exception.ToString()); + } + + textWriter.Write(messageBuilder.ToString()); + } + + /// + public void Dispose() + { + _optionsReloadToken?.Dispose(); + } +} diff --git a/src/Logging/SyslogFormatterOptions.cs b/src/Logging/SyslogFormatterOptions.cs new file mode 100644 index 00000000..e7e96eb0 --- /dev/null +++ b/src/Logging/SyslogFormatterOptions.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace OpcPlc.Logging; + +using Microsoft.Extensions.Logging.Console; + +/// +/// Options for log formatter. +/// +public sealed class SyslogFormatterOptions : ConsoleFormatterOptions +{ + /// + /// The default timestamp format for all IoT compatible logging events.. + /// + public static readonly string DefaultTimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ "; + + /// + /// Initializes a new instance of the class. + /// + public SyslogFormatterOptions() + { + ServiceId = "opcua@311"; + UseUtcTimestamp = true; + IncludeScopes = true; + TimestampFormat = DefaultTimestampFormat; + } + + /// + /// Gets the service id which is added to the syslog output, e.g. 'service@311'. + /// see https://www.iana.org/assignments/enterprise-numbers/?q=microsoft for enterprise ids. + /// + public string ServiceId { get; } +} diff --git a/src/OpcApplicationConfiguration.cs b/src/OpcApplicationConfiguration.cs index 81ae0f39..a72c77ea 100644 --- a/src/OpcApplicationConfiguration.cs +++ b/src/OpcApplicationConfiguration.cs @@ -1,8 +1,8 @@ -namespace OpcPlc; +namespace OpcPlc; +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Configuration; -using Serilog.Extensions.Logging; using System; using System.Linq; using System.Threading.Tasks; @@ -27,7 +27,7 @@ public static string Hostname ? _hostname[.._hostname.IndexOf('.')] : _hostname; public static string ApplicationName => ProgramName; - public static string ApplicationUri => $"urn:{ProgramName}:{HostnameLabel}{(string.IsNullOrEmpty(ServerPath) ? string.Empty : (ServerPath.StartsWith("/") ? string.Empty : ":"))}{ServerPath.Replace("/", ":")}"; + public static string ApplicationUri => $"urn:{ProgramName}:{HostnameLabel}{(string.IsNullOrEmpty(ServerPath) ? string.Empty : (ServerPath.StartsWith('/') ? string.Empty : ":"))}{ServerPath.Replace('/', ':')}"; public static string ProductUri => "https://github.com/azure-samples/iot-edge-opc-plc"; public static ushort ServerPort { get; set; } = 50000; public static string ServerPath { get; set; } = string.Empty; @@ -55,21 +55,6 @@ public static string Hostname /// public static int OpcMaxStringLength { get; set; } = 4 * 1024 * 1024; - /// - /// Mapping of the application logging levels to OPC stack logging levels. - /// - public static int OpcTraceToLoggerVerbose = 0; - public static int OpcTraceToLoggerDebug = 0; - public static int OpcTraceToLoggerInformation = 0; - public static int OpcTraceToLoggerWarning = 0; - public static int OpcTraceToLoggerError = 0; - public static int OpcTraceToLoggerFatal = 0; - - /// - /// Set the OPC stack log level. - /// - public static int OpcStackTraceMask { get; set; } = 0; - /// /// Configures all OPC stack settings. /// @@ -85,8 +70,24 @@ public async Task ConfigureAsync() var transportQuotas = new TransportQuotas { MaxStringLength = OpcMaxStringLength, - MaxMessageSize = 4 * 1024 * 1024, // 4 kB. - MaxByteStringLength = 4 * 1024 * 1024, // 4 kB. + MaxMessageSize = 4 * 1024 * 1024, // 4MB. + MaxByteStringLength = 4 * 1024 * 1024, // 4MB. + }; + + var operationLimits = new OperationLimits() + { + MaxMonitoredItemsPerCall = 2500, + MaxNodesPerBrowse = 2500, + MaxNodesPerHistoryReadData = 1000, + MaxNodesPerHistoryReadEvents = 1000, + MaxNodesPerHistoryUpdateData = 1000, + MaxNodesPerHistoryUpdateEvents = 1000, + MaxNodesPerMethodCall = 1000, + MaxNodesPerNodeManagement = 1000, + MaxNodesPerRead = 2500, + MaxNodesPerWrite = 1000, + MaxNodesPerRegisterNodes = 1000, + MaxNodesPerTranslateBrowsePathsToNodeIds = 1000, }; var alternateBaseAddresses = from dnsName in DnsNames @@ -101,11 +102,11 @@ public async Task ConfigureAsync() } catch (Exception ex) { - Logger.Warning(ex, "Could not get hostname."); + Logger.LogWarning(ex, "Could not get hostname."); } } - Logger.Information("Alternate base addresses (for server binding and certificate DNSNames and IPAddresses extensions): {alternateBaseAddresses}", alternateBaseAddresses); + Logger.LogInformation("Alternate base addresses (for server binding and certificate DNSNames and IPAddresses extensions): {alternateBaseAddresses}", alternateBaseAddresses); // configure OPC UA server var serverBuilder = application.Build(ApplicationUri, ProductUri) @@ -142,33 +143,28 @@ public async Task ConfigureAsync() // Set the server capabilities. .SetMaxSessionCount(MaxSessionCount) .SetMaxSubscriptionCount(MaxSubscriptionCount) - .SetMaxQueuedRequestCount(MaxQueuedRequestCount); + .SetMaxQueuedRequestCount(MaxQueuedRequestCount) + .SetOperationLimits(operationLimits); // Security configuration. ApplicationConfiguration = await InitApplicationSecurityAsync(securityBuilder).ConfigureAwait(false); foreach (var policy in ApplicationConfiguration.ServerConfiguration.SecurityPolicies) { - Logger.Information("Added security policy {securityPolicyUri} with mode {securityMode}", + Logger.LogInformation("Added security policy {securityPolicyUri} with mode {securityMode}", policy.SecurityPolicyUri, policy.SecurityMode); if (policy.SecurityMode == MessageSecurityMode.None) { - Logger.Warning("Security policy {none} is a security risk and needs to be disabled for production use", "None"); + Logger.LogWarning("Security policy {none} is a security risk and needs to be disabled for production use", "None"); } } - Logger.Information("LDS(-ME) registration interval set to {ldsRegistrationInterval} ms (0 means no registration)", + Logger.LogInformation("LDS(-ME) registration interval set to {ldsRegistrationInterval} ms (0 means no registration)", LdsRegistrationInterval); - // configure OPC stack tracing - Utils.SetTraceMask(OpcStackTraceMask); - Logger.Information("The OPC UA trace mask is set to: {opcStackTraceMask}", - $"0x{OpcStackTraceMask:X}"); - - var microsoftLogger = new SerilogLoggerFactory(Logger) - .CreateLogger("OPC"); + var microsoftLogger = Program.LoggerFactory.CreateLogger("OpcUa"); // set logger interface, disables TraceEvent Utils.SetLogger(microsoftLogger); @@ -177,7 +173,7 @@ public async Task ConfigureAsync() var certificate = ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate; if (certificate == null) { - Logger.Information("No existing application certificate found. Creating a self-signed application certificate valid since yesterday for {defaultLifeTime} months, " + + Logger.LogInformation("No existing application certificate found. Creating a self-signed application certificate valid since yesterday for {defaultLifeTime} months, " + "with a {defaultKeySize} bit key and {defaultHashSize} bit hash", CertificateFactory.DefaultLifeTime, CertificateFactory.DefaultKeySize, @@ -185,7 +181,7 @@ public async Task ConfigureAsync() } else { - Logger.Information("Application certificate with thumbprint {thumbprint} found in the application certificate store", + Logger.LogInformation("Application certificate with thumbprint {thumbprint} found in the application certificate store", certificate.Thumbprint); } @@ -199,11 +195,11 @@ public async Task ConfigureAsync() if (certificate == null) { certificate = ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate; - Logger.Information("Application certificate with thumbprint {thumbprint} created", + Logger.LogInformation("Application certificate with thumbprint {thumbprint} created", certificate.Thumbprint); } - Logger.Information("Application certificate is for ApplicationUri {applicationUri}, ApplicationName {applicationName} and Subject is {subject}", + Logger.LogInformation("Application certificate is for ApplicationUri {applicationUri}, ApplicationName {applicationName} and Subject is {subject}", ApplicationConfiguration.ApplicationUri, ApplicationConfiguration.ApplicationName, ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate.Subject); @@ -216,7 +212,7 @@ public async Task ConfigureAsync() // show certificate store information await ShowCertificateStoreInformationAsync().ConfigureAwait(false); - Logger.Information("Application configured with MaxSessionCount {maxSessionCount} and MaxSubscriptionCount {maxSubscriptionCount}", + Logger.LogInformation("Application configured with MaxSessionCount {maxSessionCount} and MaxSubscriptionCount {maxSubscriptionCount}", ApplicationConfiguration.ServerConfiguration.MaxSessionCount, ApplicationConfiguration.ServerConfiguration.MaxSubscriptionCount); diff --git a/src/OpcApplicationConfigurationSecurity.cs b/src/OpcApplicationConfigurationSecurity.cs index 26606b8a..8f273d34 100644 --- a/src/OpcApplicationConfigurationSecurity.cs +++ b/src/OpcApplicationConfigurationSecurity.cs @@ -1,4 +1,6 @@ namespace OpcPlc; + +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Configuration; using Opc.Ua.Security.Certificates; @@ -140,28 +142,28 @@ public async Task InitApplicationSecurityAsync(IApplic } ApplicationConfiguration = await options.Create().ConfigureAwait(false); - Logger.Information("Application Certificate store type is: {storeType}", id.StoreType); - Logger.Information("Application Certificate store path is: {storePath}", id.StorePath); + Logger.LogInformation("Application Certificate store type is: {storeType}", id.StoreType); + Logger.LogInformation("Application Certificate store path is: {storePath}", id.StorePath); - Logger.Information("Rejection of SHA1 signed certificates is {status}", + Logger.LogInformation("Rejection of SHA1 signed certificates is {status}", securityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled"); - Logger.Information("Minimum certificate key size set to {minimumCertificateKeySize}", + Logger.LogInformation("Minimum certificate key size set to {minimumCertificateKeySize}", securityConfiguration.MinimumCertificateKeySize); - Logger.Information("Trusted Issuer store type is: {storeType}", issuerList.StoreType); - Logger.Information("Trusted Issuer Certificate store path is: {storePath}", issuerList.StorePath); + Logger.LogInformation("Trusted Issuer store type is: {storeType}", issuerList.StoreType); + Logger.LogInformation("Trusted Issuer Certificate store path is: {storePath}", issuerList.StorePath); - Logger.Information("Trusted Peer Certificate store type is: {storeType}", trustList.StoreType); - Logger.Information("Trusted Peer Certificate store path is: {storePath}", trustList.StorePath); + Logger.LogInformation("Trusted Peer Certificate store type is: {storeType}", trustList.StoreType); + Logger.LogInformation("Trusted Peer Certificate store path is: {storePath}", trustList.StorePath); - Logger.Information("Rejected certificate store type is: {storeType}", rejectedStore.StoreType); - Logger.Information("Rejected Certificate store path is: {storePath}", rejectedStore.StorePath); + Logger.LogInformation("Rejected certificate store type is: {storeType}", rejectedStore.StoreType); + Logger.LogInformation("Rejected Certificate store path is: {storePath}", rejectedStore.StorePath); // handle cert validation if (AutoAcceptCerts) { - Logger.Warning("Automatically accepting all client certificates, this is a security risk!"); + Logger.LogWarning("Automatically accepting all client certificates, this is a security risk!"); } ApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); @@ -220,7 +222,7 @@ public static async Task ShowCreateSigningRequestInformationAsync(X509Certificat } catch (Exception e) { - Logger.Error(e, "Error while loading private key"); + Logger.LogError(e, "Error while loading private key"); return; } } @@ -231,44 +233,44 @@ public static async Task ShowCreateSigningRequestInformationAsync(X509Certificat } catch (Exception e) { - Logger.Error(e, "Error while creating signing request"); + Logger.LogError(e, "Error while creating signing request"); return; } - Logger.Information("----------------------- CreateSigningRequest information ------------------"); - Logger.Information("ApplicationUri: {applicationUri}", ApplicationConfiguration.ApplicationUri); - Logger.Information("ApplicationName: {applicationName}", ApplicationConfiguration.ApplicationName); - Logger.Information("ApplicationType: {applicationType}", ApplicationConfiguration.ApplicationType); - Logger.Information("ProductUri: {productUri}", ApplicationConfiguration.ProductUri); + Logger.LogInformation("----------------------- CreateSigningRequest information ------------------"); + Logger.LogInformation("ApplicationUri: {applicationUri}", ApplicationConfiguration.ApplicationUri); + Logger.LogInformation("ApplicationName: {applicationName}", ApplicationConfiguration.ApplicationName); + Logger.LogInformation("ApplicationType: {applicationType}", ApplicationConfiguration.ApplicationType); + Logger.LogInformation("ProductUri: {productUri}", ApplicationConfiguration.ProductUri); if (ApplicationConfiguration.ApplicationType != ApplicationType.Client) { int serverNum = 0; foreach (var endpoint in ApplicationConfiguration.ServerConfiguration.BaseAddresses) { - Logger.Information($"DiscoveryUrl[{serverNum++}]: {endpoint}"); + Logger.LogInformation($"DiscoveryUrl[{serverNum++}]: {endpoint}"); } foreach (var endpoint in ApplicationConfiguration.ServerConfiguration.AlternateBaseAddresses) { - Logger.Information($"DiscoveryUrl[{serverNum++}]: {endpoint}"); + Logger.LogInformation($"DiscoveryUrl[{serverNum++}]: {endpoint}"); } string[] serverCapabilities = ApplicationConfiguration.ServerConfiguration.ServerCapabilities.ToArray(); - Logger.Information($"ServerCapabilities: {string.Join(", ", serverCapabilities)}"); + Logger.LogInformation($"ServerCapabilities: {string.Join(", ", serverCapabilities)}"); } - Logger.Information("CSR (base64 encoded):"); + Logger.LogInformation("CSR (base64 encoded):"); Console.WriteLine($"{Convert.ToBase64String(certificateSigningRequest)}"); - Logger.Information("---------------------------------------------------------------------------"); + Logger.LogInformation("---------------------------------------------------------------------------"); try { await File.WriteAllBytesAsync($"{ApplicationConfiguration.ApplicationName}.csr", certificateSigningRequest).ConfigureAwait(false); - Logger.Information($"Binary CSR written to '{ApplicationConfiguration.ApplicationName}.csr'"); + Logger.LogInformation($"Binary CSR written to '{ApplicationConfiguration.ApplicationName}.csr'"); } catch (Exception e) { - Logger.Error(e, "Error while writing .csr file"); + Logger.LogError(e, "Error while writing .csr file"); } } catch (Exception e) { - Logger.Error(e, "Error in CSR creation"); + Logger.LogError(e, "Error in CSR creation"); } } @@ -283,10 +285,10 @@ public static async Task ShowCertificateStoreInformationAsync() using ICertificateStore certStore = ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.OpenStore(); var certs = await certStore.Enumerate().ConfigureAwait(false); int certNum = 1; - Logger.Information("Application store contains {count} certs", certs.Count); + Logger.LogInformation("Application store contains {count} certs", certs.Count); foreach (var cert in certs) { - Logger.Information("{index}: Subject {subject} (thumbprint: {thumbprint})", + Logger.LogInformation("{index}: Subject {subject} (thumbprint: {thumbprint})", $"{certNum++:D2}", cert.Subject, cert.GetCertHashString()); @@ -294,7 +296,7 @@ public static async Task ShowCertificateStoreInformationAsync() } catch (Exception e) { - Logger.Error(e, "Error while trying to read information from application store"); + Logger.LogError(e, "Error while trying to read information from application store"); } // show trusted issuer certs @@ -303,10 +305,10 @@ public static async Task ShowCertificateStoreInformationAsync() using ICertificateStore certStore = ApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.OpenStore(); var certs = await certStore.Enumerate().ConfigureAwait(false); int certNum = 1; - Logger.Information("Trusted issuer store contains {count} certs", certs.Count); + Logger.LogInformation("Trusted issuer store contains {count} certs", certs.Count); foreach (var cert in certs) { - Logger.Information("{index}: Subject {subject} (thumbprint: {thumbprint})", + Logger.LogInformation("{index}: Subject {subject} (thumbprint: {thumbprint})", $"{certNum++:D2}", cert.Subject, cert.GetCertHashString()); @@ -316,10 +318,10 @@ public static async Task ShowCertificateStoreInformationAsync() { var crls = await certStore.EnumerateCRLs().ConfigureAwait(false); int crlNum = 1; - Logger.Information("Trusted issuer store has {count} CRLs", crls.Count); + Logger.LogInformation("Trusted issuer store has {count} CRLs", crls.Count); foreach (var crl in crls) { - Logger.Information("{index}: Issuer {issuer}, Next update time {nextUpdate}", + Logger.LogInformation("{index}: Issuer {issuer}, Next update time {nextUpdate}", $"{crlNum++:D2}", crl.Issuer, crl.NextUpdate); @@ -328,7 +330,7 @@ public static async Task ShowCertificateStoreInformationAsync() } catch (Exception e) { - Logger.Error(e, "Error while trying to read information from trusted issuer store"); + Logger.LogError(e, "Error while trying to read information from trusted issuer store"); } // show trusted peer certs @@ -337,10 +339,10 @@ public static async Task ShowCertificateStoreInformationAsync() using ICertificateStore certStore = ApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); var certs = await certStore.Enumerate().ConfigureAwait(false); int certNum = 1; - Logger.Information("Trusted peer store contains {count} certs", certs.Count); + Logger.LogInformation("Trusted peer store contains {count} certs", certs.Count); foreach (var cert in certs) { - Logger.Information("{index}: Subject {subject} (thumbprint: {thumbprint})", + Logger.LogInformation("{index}: Subject {subject} (thumbprint: {thumbprint})", $"{certNum++:D2}", cert.Subject, cert.GetCertHashString()); @@ -350,10 +352,10 @@ public static async Task ShowCertificateStoreInformationAsync() { var crls = await certStore.EnumerateCRLs().ConfigureAwait(false); int crlNum = 1; - Logger.Information("Trusted peer store has {count} CRLs", crls.Count); + Logger.LogInformation("Trusted peer store has {count} CRLs", crls.Count); foreach (var crl in crls) { - Logger.Information("{index}: Issuer {issuer}, Next update time {nextUpdate}", + Logger.LogInformation("{index}: Issuer {issuer}, Next update time {nextUpdate}", $"{crlNum++:D2}", crl.Issuer, crl.NextUpdate); @@ -362,7 +364,7 @@ public static async Task ShowCertificateStoreInformationAsync() } catch (Exception e) { - Logger.Error(e, "Error while trying to read information from trusted peer store"); + Logger.LogError(e, "Error while trying to read information from trusted peer store"); } // show rejected peer certs @@ -371,10 +373,10 @@ public static async Task ShowCertificateStoreInformationAsync() using ICertificateStore certStore = ApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.OpenStore(); var certs = await certStore.Enumerate().ConfigureAwait(false); int certNum = 1; - Logger.Information("Rejected certificate store contains {count} certs", certs.Count); + Logger.LogInformation("Rejected certificate store contains {count} certs", certs.Count); foreach (var cert in certs) { - Logger.Information("{index}: Subject {subject} (thumbprint: {thumbprint})", + Logger.LogInformation("{index}: Subject {subject} (thumbprint: {thumbprint})", $"{certNum++:D2}", cert.Subject, cert.GetCertHashString()); @@ -382,7 +384,7 @@ public static async Task ShowCertificateStoreInformationAsync() } catch (Exception e) { - Logger.Error(e, "Error while trying to read information from rejected certificate store"); + Logger.LogError(e, "Error while trying to read information from rejected certificate store"); } } @@ -396,11 +398,11 @@ private static void CertificateValidator_CertificateValidation(CertificateValida e.Accept = AutoAcceptCerts; if (AutoAcceptCerts) { - Logger.Warning("Trusting certificate {certificateSubject} because of corresponding command line option", e.Certificate.Subject); + Logger.LogWarning("Trusting certificate {certificateSubject} because of corresponding command line option", e.Certificate.Subject); } else { - Logger.Error( + Logger.LogError( "Rejecting OPC application with certificate {certificateSubject}. If you want to trust this certificate, please copy it from the directory {rejectedCertificateStore} to {trustedPeerCertificates}", e.Certificate.Subject, $"{ApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath}{Path.DirectorySeparatorChar}certs", @@ -418,14 +420,14 @@ private async Task RemoveCertificatesAsync(List thumbprintsToRemov if (thumbprintsToRemove.Count == 0) { - Logger.Error("There is no thumbprint specified for certificates to remove. Please check your command line options."); + Logger.LogError("There is no thumbprint specified for certificates to remove. Please check your command line options."); return false; } // search the trusted peer store and remove certificates with a specified thumbprint try { - Logger.Information("Starting to remove certificate(s) from trusted peer and trusted issuer store"); + Logger.LogInformation("Starting to remove certificate(s) from trusted peer and trusted issuer store"); using ICertificateStore trustedStore = CertificateStoreIdentifier.OpenStore(ApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath); foreach (var thumbprint in thumbprintsToRemove) { @@ -434,18 +436,18 @@ private async Task RemoveCertificatesAsync(List thumbprintsToRemov { if (!await trustedStore.Delete(thumbprint).ConfigureAwait(false)) { - Logger.Warning($"Failed to remove certificate with thumbprint '{thumbprint}' from the trusted peer store"); + Logger.LogWarning($"Failed to remove certificate with thumbprint '{thumbprint}' from the trusted peer store"); } else { - Logger.Information($"Removed certificate with thumbprint '{thumbprint}' from the trusted peer store"); + Logger.LogInformation($"Removed certificate with thumbprint '{thumbprint}' from the trusted peer store"); } } } } catch (Exception e) { - Logger.Error(e, "Error while trying to remove certificate(s) from the trusted peer store"); + Logger.LogError(e, "Error while trying to remove certificate(s) from the trusted peer store"); result = false; } @@ -460,18 +462,18 @@ private async Task RemoveCertificatesAsync(List thumbprintsToRemov { if (!await issuerStore.Delete(thumbprint).ConfigureAwait(false)) { - Logger.Warning($"Failed to delete certificate with thumbprint '{thumbprint}' from the trusted issuer store"); + Logger.LogWarning($"Failed to delete certificate with thumbprint '{thumbprint}' from the trusted issuer store"); } else { - Logger.Information($"Removed certificate with thumbprint '{thumbprint}' from the trusted issuer store"); + Logger.LogInformation($"Removed certificate with thumbprint '{thumbprint}' from the trusted issuer store"); } } } } catch (Exception e) { - Logger.Error(e, "Error while trying to remove certificate(s) from the trusted issuer store"); + Logger.LogError(e, "Error while trying to remove certificate(s) from the trusted issuer store"); result = false; } return result; @@ -489,11 +491,11 @@ private async Task AddCertificatesAsync( if (certificateBase64Strings?.Count == 0 && certificateFileNames?.Count == 0) { - Logger.Error("There is no certificate provided. Please check your command line options."); + Logger.LogError("There is no certificate provided. Please check your command line options."); return false; } - Logger.Information($"Starting to add certificate(s) to the {(issuerCertificate ? "trusted issuer" : "trusted peer")} store"); + Logger.LogInformation($"Starting to add certificate(s) to the {(issuerCertificate ? "trusted issuer" : "trusted peer")} store"); var certificatesToAdd = new X509Certificate2Collection(); try { @@ -518,7 +520,7 @@ private async Task AddCertificatesAsync( } else { - Logger.Error($"The provided string '{certificateBase64String.Substring(0, 10)}...' is not a valid base64 string"); + Logger.LogError($"The provided string '{certificateBase64String.Substring(0, 10)}...' is not a valid base64 string"); return false; } } @@ -526,7 +528,7 @@ private async Task AddCertificatesAsync( } catch (Exception e) { - Logger.Error(e, "The issuer certificate data is invalid. Please check your command line options"); + Logger.LogError(e, "The issuer certificate data is invalid. Please check your command line options"); return false; } @@ -541,18 +543,18 @@ private async Task AddCertificatesAsync( try { await issuerStore.Add(certificateToAdd).ConfigureAwait(false); - Logger.Information($"Certificate '{certificateToAdd.SubjectName.Name}' and thumbprint '{certificateToAdd.Thumbprint}' was added to the trusted issuer store"); + Logger.LogInformation($"Certificate '{certificateToAdd.SubjectName.Name}' and thumbprint '{certificateToAdd.Thumbprint}' was added to the trusted issuer store"); } catch (ArgumentException) { // ignore error if cert already exists in store - Logger.Information($"Certificate '{certificateToAdd.SubjectName.Name}' already exists in trusted issuer store"); + Logger.LogInformation($"Certificate '{certificateToAdd.SubjectName.Name}' already exists in trusted issuer store"); } } } catch (Exception e) { - Logger.Error(e, "Error while adding a certificate to the trusted issuer store"); + Logger.LogError(e, "Error while adding a certificate to the trusted issuer store"); result = false; } } @@ -566,18 +568,18 @@ private async Task AddCertificatesAsync( try { await trustedStore.Add(certificateToAdd).ConfigureAwait(false); - Logger.Information($"Certificate '{certificateToAdd.SubjectName.Name}' and thumbprint '{certificateToAdd.Thumbprint}' was added to the trusted peer store"); + Logger.LogInformation($"Certificate '{certificateToAdd.SubjectName.Name}' and thumbprint '{certificateToAdd.Thumbprint}' was added to the trusted peer store"); } catch (ArgumentException) { // ignore error if cert already exists in store - Logger.Information($"Certificate '{certificateToAdd.SubjectName.Name}' already exists in trusted peer store"); + Logger.LogInformation($"Certificate '{certificateToAdd.SubjectName.Name}' already exists in trusted peer store"); } } } catch (Exception e) { - Logger.Error(e, "Error while adding a certificate to the trusted peer store"); + Logger.LogError(e, "Error while adding a certificate to the trusted peer store"); result = false; } } @@ -593,12 +595,12 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl if (string.IsNullOrEmpty(newCrlBase64String) && string.IsNullOrEmpty(newCrlFileName)) { - Logger.Error("There is no CRL specified. Please check your command line options"); + Logger.LogError("There is no CRL specified. Please check your command line options"); return false; } // validate input and create the new CRL - Logger.Information("Starting to update the current CRL"); + Logger.LogInformation("Starting to update the current CRL"); X509CRL newCrl; try { @@ -611,7 +613,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl } else { - Logger.Error($"The provided string '{newCrlBase64String.Substring(0, 10)}...' is not a valid base64 string"); + Logger.LogError($"The provided string '{newCrlBase64String.Substring(0, 10)}...' is not a valid base64 string"); return false; } } @@ -622,7 +624,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl } catch (Exception e) { - Logger.Error(e, "The new CRL data is invalid"); + Logger.LogError(e, "The new CRL data is invalid"); return false; } @@ -638,7 +640,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl if (X509Utils.CompareDistinguishedName(newCrl.Issuer, trustedCertificate.Subject) && newCrl.VerifySignature(trustedCertificate, false)) { // the issuer of the new CRL is trusted. delete the crls of the issuer in the trusted store - Logger.Information("Remove the current CRL from the trusted peer store"); + Logger.LogInformation("Remove the current CRL from the trusted peer store"); trustedCrlIssuer = true; var crlsToRemove = await trustedStore.EnumerateCRLs(trustedCertificate).ConfigureAwait(false); @@ -648,12 +650,12 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl { if (!await trustedStore.DeleteCRL(crlToRemove).ConfigureAwait(false)) { - Logger.Warning($"Failed to remove CRL issued by '{crlToRemove.Issuer}' from the trusted peer store"); + Logger.LogWarning($"Failed to remove CRL issued by '{crlToRemove.Issuer}' from the trusted peer store"); } } catch (Exception e) { - Logger.Error(e, $"Error while removing the current CRL issued by '{crlToRemove.Issuer}' from the trusted peer store"); + Logger.LogError(e, $"Error while removing the current CRL issued by '{crlToRemove.Issuer}' from the trusted peer store"); result = false; } } @@ -661,7 +663,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl } catch (Exception e) { - Logger.Error(e, "Error while removing the cureent CRL from the trusted peer store"); + Logger.LogError(e, "Error while removing the cureent CRL from the trusted peer store"); result = false; } } @@ -671,11 +673,11 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl try { await trustedStore.AddCRL(newCrl).ConfigureAwait(false); - Logger.Information($"The new CRL issued by '{newCrl.Issuer}' was added to the trusted peer store"); + Logger.LogInformation($"The new CRL issued by '{newCrl.Issuer}' was added to the trusted peer store"); } catch (Exception e) { - Logger.Error(e, "Error while adding the new CRL to the trusted peer store"); + Logger.LogError(e, "Error while adding the new CRL to the trusted peer store"); result = false; } } @@ -693,7 +695,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl if (X509Utils.CompareDistinguishedName(newCrl.Issuer, issuerCertificate.Subject) && newCrl.VerifySignature(issuerCertificate, false)) { // the issuer of the new CRL is trusted. delete the crls of the issuer in the trusted store - Logger.Information("Remove the current CRL from the trusted issuer store"); + Logger.LogInformation("Remove the current CRL from the trusted issuer store"); trustedCrlIssuer = true; var crlsToRemove = await issuerStore.EnumerateCRLs(issuerCertificate).ConfigureAwait(false); foreach (var crlToRemove in crlsToRemove) @@ -702,12 +704,12 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl { if (!await issuerStore.DeleteCRL(crlToRemove).ConfigureAwait(false)) { - Logger.Warning($"Failed to remove the current CRL issued by '{crlToRemove.Issuer}' from the trusted issuer store"); + Logger.LogWarning($"Failed to remove the current CRL issued by '{crlToRemove.Issuer}' from the trusted issuer store"); } } catch (Exception e) { - Logger.Error(e, $"Error while removing the current CRL issued by '{crlToRemove.Issuer}' from the trusted issuer store"); + Logger.LogError(e, $"Error while removing the current CRL issued by '{crlToRemove.Issuer}' from the trusted issuer store"); result = false; } } @@ -715,7 +717,7 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl } catch (Exception e) { - Logger.Error(e, "Error while removing the current CRL from the trusted issuer store"); + Logger.LogError(e, "Error while removing the current CRL from the trusted issuer store"); result = false; } } @@ -726,11 +728,11 @@ private async Task UpdateCrlAsync(string newCrlBase64String, string newCrl try { await issuerStore.AddCRL(newCrl).ConfigureAwait(false); - Logger.Information($"The new CRL issued by '{newCrl.Issuer}' was added to the trusted issuer store"); + Logger.LogInformation($"The new CRL issued by '{newCrl.Issuer}' was added to the trusted issuer store"); } catch (Exception e) { - Logger.Error(e, $"Error while adding the new CRL issued by '{newCrl.Issuer}' to the trusted issuer store"); + Logger.LogError(e, $"Error while adding the new CRL issued by '{newCrl.Issuer}' to the trusted issuer store"); result = false; } } @@ -750,7 +752,7 @@ private async Task UpdateApplicationCertificateAsync( { if (string.IsNullOrEmpty(newCertificateFileName) && string.IsNullOrEmpty(newCertificateBase64String)) { - Logger.Error("There is no new application certificate data provided. Please check your command line options."); + Logger.LogError("There is no new application certificate data provided. Please check your command line options."); return false; } @@ -767,7 +769,7 @@ private async Task UpdateApplicationCertificateAsync( } else { - Logger.Error($"The provided string '{newCertificateBase64String.Substring(0, 10)}...' is not a valid base64 string"); + Logger.LogError($"The provided string '{newCertificateBase64String.Substring(0, 10)}...' is not a valid base64 string"); return false; } } @@ -778,12 +780,12 @@ private async Task UpdateApplicationCertificateAsync( } catch (Exception e) { - Logger.Error(e, "The new application certificate data is invalid"); + Logger.LogError(e, "The new application certificate data is invalid"); return false; } // validate input and create the private key - Logger.Information("Start updating the current application certificate"); + Logger.LogInformation("Start updating the current application certificate"); byte[] privateKey = null; try { @@ -792,7 +794,7 @@ private async Task UpdateApplicationCertificateAsync( privateKey = new byte[privateKeyBase64String.Length * 3 / 4]; if (!Convert.TryFromBase64String(privateKeyBase64String, privateKey, out int written)) { - Logger.Error($"The provided string '{privateKeyBase64String.Substring(0, 10)}...' is not a valid base64 string"); + Logger.LogError($"The provided string '{privateKeyBase64String.Substring(0, 10)}...' is not a valid base64 string"); return false; } } @@ -803,7 +805,7 @@ private async Task UpdateApplicationCertificateAsync( } catch (Exception e) { - Logger.Error(e, "The private key data is invalid"); + Logger.LogError(e, "The private key data is invalid"); return false; } @@ -816,17 +818,17 @@ private async Task UpdateApplicationCertificateAsync( hasApplicationCertificate = true; currentApplicationCertificate = ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate; currentSubjectName = currentApplicationCertificate.SubjectName.Name; - Logger.Information($"The current application certificate has SubjectName '{currentSubjectName}' and thumbprint '{currentApplicationCertificate.Thumbprint}'"); + Logger.LogInformation($"The current application certificate has SubjectName '{currentSubjectName}' and thumbprint '{currentApplicationCertificate.Thumbprint}'"); } else { - Logger.Information("There is no existing application certificate"); + Logger.LogInformation("There is no existing application certificate"); } // for a cert update subject names of current and new certificate must match if (hasApplicationCertificate && !X509Utils.CompareDistinguishedName(currentSubjectName, newCertificate.SubjectName.Name)) { - Logger.Error($"The SubjectName '{newCertificate.SubjectName.Name}' of the new certificate doesn't match the current certificates SubjectName '{currentSubjectName}'"); + Logger.LogError($"The SubjectName '{newCertificate.SubjectName.Name}' of the new certificate doesn't match the current certificates SubjectName '{currentSubjectName}'"); return false; } @@ -862,7 +864,7 @@ private async Task UpdateApplicationCertificateAsync( } catch (Exception e) { - Logger.Error(e, "Failed to verify integrity of the new certificate and the trusted issuer list"); + Logger.LogError(e, "Failed to verify integrity of the new certificate and the trusted issuer list"); return false; } @@ -877,11 +879,11 @@ private async Task UpdateApplicationCertificateAsync( X509Certificate2 certWithPrivateKey = X509Utils.CreateCertificateFromPKCS12(privateKey, certificatePassword); newCertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPrivateKey(newCertificate, certWithPrivateKey); newCertFormat = "PFX"; - Logger.Information("The private key for the new certificate was passed in using PFX format"); + Logger.LogInformation("The private key for the new certificate was passed in using PFX format"); } catch { - Logger.Debug("Certificate file is not PFX"); + Logger.LogDebug("Certificate file is not PFX"); } } // check if new cert is PEM @@ -891,11 +893,11 @@ private async Task UpdateApplicationCertificateAsync( { newCertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(newCertificate, privateKey, certificatePassword); newCertFormat = "PEM"; - Logger.Information("The private key for the new certificate was passed in using PEM format"); + Logger.LogInformation("The private key for the new certificate was passed in using PEM format"); } catch { - Logger.Debug("Certificate file is not PEM"); + Logger.LogDebug("Certificate file is not PEM"); } } if (string.IsNullOrEmpty(newCertFormat)) @@ -911,12 +913,12 @@ private async Task UpdateApplicationCertificateAsync( } else { - Logger.Error("There is no existing application certificate we can use to extract the private key. You need to pass in a private key using PFX or PEM format"); + Logger.LogError("There is no existing application certificate we can use to extract the private key. You need to pass in a private key using PFX or PEM format"); } } catch { - Logger.Debug("Application certificate format is not DER"); + Logger.LogDebug("Application certificate format is not DER"); } } @@ -925,7 +927,7 @@ private async Task UpdateApplicationCertificateAsync( { if (string.IsNullOrEmpty(newCertFormat)) { - Logger.Error("The provided format of the private key is not supported (must be PEM or PFX) or the provided cert password is wrong"); + Logger.LogError("The provided format of the private key is not supported (must be PEM or PFX) or the provided cert password is wrong"); return false; } } @@ -933,7 +935,7 @@ private async Task UpdateApplicationCertificateAsync( { if (string.IsNullOrEmpty(newCertFormat)) { - Logger.Error("There is no application certificate we can update and for the new application certificate there was not usable private key (must be PEM or PFX format) provided or the provided cert password is wrong"); + Logger.LogError("There is no application certificate we can update and for the new application certificate there was not usable private key (must be PEM or PFX format) provided or the provided cert password is wrong"); return false; } } @@ -941,26 +943,26 @@ private async Task UpdateApplicationCertificateAsync( // remove the existing and add the new application cert using (ICertificateStore appStore = CertificateStoreIdentifier.OpenStore(ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath)) { - Logger.Information("Remove the existing application certificate"); + Logger.LogInformation("Remove the existing application certificate"); try { if (hasApplicationCertificate && !await appStore.Delete(currentApplicationCertificate.Thumbprint).ConfigureAwait(false)) { - Logger.Warning($"Removing the existing application certificate with thumbprint '{currentApplicationCertificate.Thumbprint}' failed"); + Logger.LogWarning($"Removing the existing application certificate with thumbprint '{currentApplicationCertificate.Thumbprint}' failed"); } } catch { - Logger.Warning("Failed to remove the existing application certificate from the ApplicationCertificate store"); + Logger.LogWarning("Failed to remove the existing application certificate from the ApplicationCertificate store"); } try { await appStore.Add(newCertificateWithPrivateKey).ConfigureAwait(false); - Logger.Information($"The new application certificate '{newCertificateWithPrivateKey.SubjectName.Name}' and thumbprint '{newCertificateWithPrivateKey.Thumbprint}' was added to the application certificate store"); + Logger.LogInformation($"The new application certificate '{newCertificateWithPrivateKey.SubjectName.Name}' and thumbprint '{newCertificateWithPrivateKey.Thumbprint}' was added to the application certificate store"); } catch (Exception e) { - Logger.Error(e, "Failed to add the new application certificate to the application certificate store"); + Logger.LogError(e, "Failed to add the new application certificate to the application certificate store"); return false; } } @@ -968,13 +970,13 @@ private async Task UpdateApplicationCertificateAsync( // update the application certificate try { - Logger.Information($"Activating the new application certificate with thumbprint '{newCertificateWithPrivateKey.Thumbprint}'"); + Logger.LogInformation($"Activating the new application certificate with thumbprint '{newCertificateWithPrivateKey.Thumbprint}'"); ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate = newCertificate; await ApplicationConfiguration.CertificateValidator.UpdateCertificate(ApplicationConfiguration.SecurityConfiguration).ConfigureAwait(false); } catch (Exception e) { - Logger.Error(e, "Failed to activate the new application certificate"); + Logger.LogError(e, "Failed to activate the new application certificate"); return false; } diff --git a/src/PlcNodeManager.cs b/src/PlcNodeManager.cs index 55d96a6e..2fea7ebc 100644 --- a/src/PlcNodeManager.cs +++ b/src/PlcNodeManager.cs @@ -1,5 +1,6 @@ namespace OpcPlc; +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Server; using System; @@ -67,7 +68,7 @@ public override void CreateAddressSpace(IDictionary> e } catch (Exception e) { - Logger.Error(e, "Error creating address space"); + Logger.LogError(e, "Error creating address space"); } AddPredefinedNode(SystemContext, root); @@ -182,7 +183,7 @@ private BaseDataVariableState CreateBaseVariable(BaseDataVariableState baseDataV } else { - Logger.Debug("NodeId type is {nodeIdType}", path.GetType().ToString()); + Logger.LogDebug("NodeId type is {nodeIdType}", (string)path.GetType().ToString()); baseDataVariableState.NodeId = new NodeId(path, namespaceIndex); baseDataVariableState.BrowseName = new QualifiedName(name, namespaceIndex); } diff --git a/src/PlcServer.cs b/src/PlcServer.cs index 49f42c96..712823bc 100644 --- a/src/PlcServer.cs +++ b/src/PlcServer.cs @@ -1,7 +1,9 @@ namespace OpcPlc; using AlarmCondition; +using Microsoft.Extensions.Logging; using Opc.Ua; +using Opc.Ua.Bindings; using Opc.Ua.Server; using OpcPlc.CompanionSpecs.DI; using OpcPlc.DeterministicAlarms; @@ -12,6 +14,7 @@ namespace OpcPlc; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Threading; using static Program; @@ -80,13 +83,13 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser if (string.IsNullOrWhiteSpace(scriptFileName)) { string errorMessage = "The script file for deterministic testing is not set (deterministicalarms)."; - Logger.Error(errorMessage); + Logger.LogError(errorMessage); throw new Exception(errorMessage); } if (!File.Exists(scriptFileName)) { string errorMessage = $"The script file ({scriptFileName}) for deterministic testing does not exist."; - Logger.Error(errorMessage); + Logger.LogError(errorMessage); throw new Exception(errorMessage); } @@ -113,8 +116,7 @@ protected override ServerProperties LoadServerProperties() string opcUaSdkVersion = Utils.GetAssemblySoftwareVersion(); string opcUaSdkBuildNumber = opcUaSdkVersion[(opcUaSdkVersion.IndexOf('+') + 1)..]; - var properties = new ServerProperties - { + var properties = new ServerProperties { ManufacturerName = "Microsoft", ProductName = "IoT Edge OPC UA PLC", ProductUri = "https://github.com/Azure-Samples/iot-edge-opc-plc", @@ -167,6 +169,22 @@ protected override void OnServerStarted(IServerInternal server) // request notifications when the user identity is changed, all valid users are accepted by default. server.SessionManager.ImpersonateUser += new ImpersonateEventHandler(SessionManager_ImpersonateUser); + } + + /// + protected override void ProcessRequest(IEndpointIncomingRequest request, object callData) + { + if (request is IAsyncResult asyncResult && + asyncResult.AsyncState is object[] asyncStateArray && + asyncStateArray[0] is TcpServerChannel channel) + { + using var scope = Logger.BeginScope("ChannelId:\"{ChannelId}\"", channel.Id); + base.ProcessRequest(request, callData); + } + else + { + base.ProcessRequest(request, callData); + } } /// diff --git a/src/PluginNodes/Boiler2PluginNodes.cs b/src/PluginNodes/Boiler2PluginNodes.cs index d979b302..08061495 100644 --- a/src/PluginNodes/Boiler2PluginNodes.cs +++ b/src/PluginNodes/Boiler2PluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.DI; using OpcPlc.Helpers; @@ -232,7 +233,7 @@ private void AddMethods() private ServiceResult SwitchOnCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { SetValue(_heaterStateNode, inputArguments.First()); - Logger.Debug($"SwitchOnCall method called with argument: {inputArguments.First()}"); + Logger.LogDebug($"SwitchOnCall method called with argument: {inputArguments.First()}"); return ServiceResult.Good; } diff --git a/src/PluginNodes/ComplexTypeBoilerPluginNode.cs b/src/PluginNodes/ComplexTypeBoilerPluginNode.cs index 7e576745..e9fa0a07 100644 --- a/src/PluginNodes/ComplexTypeBoilerPluginNode.cs +++ b/src/PluginNodes/ComplexTypeBoilerPluginNode.cs @@ -1,6 +1,7 @@ namespace OpcPlc.PluginNodes; using BoilerModel1; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -166,7 +167,7 @@ private void SetHeaterOffMethodProperties(ref MethodState method) private ServiceResult OnHeaterOnCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _node.BoilerStatus.Value.HeaterState = BoilerHeaterStateType.On; - Logger.Debug("OnHeaterOnCall method called"); + Logger.LogDebug("OnHeaterOnCall method called"); return ServiceResult.Good; } @@ -176,7 +177,7 @@ private ServiceResult OnHeaterOnCall(ISystemContext context, MethodState method, private ServiceResult OnHeaterOffCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _node.BoilerStatus.Value.HeaterState = BoilerHeaterStateType.Off; - Logger.Debug("OnHeaterOffCall method called"); + Logger.LogDebug("OnHeaterOffCall method called"); return ServiceResult.Good; } } diff --git a/src/PluginNodes/DataPluginNodes.cs b/src/PluginNodes/DataPluginNodes.cs index 7bddb724..6c9cdc82 100644 --- a/src/PluginNodes/DataPluginNodes.cs +++ b/src/PluginNodes/DataPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.PluginNodes.Models; using System; @@ -179,7 +180,7 @@ private void SetStopStepUpMethodProperties(ref MethodState method) private ServiceResult OnResetStepUpCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { ResetStepUpData(); - Logger.Debug("ResetStepUp method called"); + Logger.LogDebug("ResetStepUp method called"); return ServiceResult.Good; } @@ -189,7 +190,7 @@ private ServiceResult OnResetStepUpCall(ISystemContext context, MethodState meth private ServiceResult OnStartStepUpCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { StartStepUp(); - Logger.Debug("StartStepUp method called"); + Logger.LogDebug("StartStepUp method called"); return ServiceResult.Good; } @@ -199,7 +200,7 @@ private ServiceResult OnStartStepUpCall(ISystemContext context, MethodState meth private ServiceResult OnStopStepUpCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { StopStepUp(); - Logger.Debug("StopStepUp method called"); + Logger.LogDebug("StopStepUp method called"); return ServiceResult.Good; } @@ -234,7 +235,7 @@ private bool AlternatingBooleanGenerator(bool value) bool nextAlternatingBoolean = _alternatingBooleanCycleInPhase % PlcSimulation.SimulationCycleCount == 0 ? !value : value; if (value != nextAlternatingBoolean) { - Logger.Verbose($"Data change to: {nextAlternatingBoolean}"); + Logger.LogTrace($"Data change to: {nextAlternatingBoolean}"); } // end of cycle: reset cycle count diff --git a/src/PluginNodes/DeterministicGuidPluginNodes.cs b/src/PluginNodes/DeterministicGuidPluginNodes.cs index d4022a04..a5d345dd 100644 --- a/src/PluginNodes/DeterministicGuidPluginNodes.cs +++ b/src/PluginNodes/DeterministicGuidPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -65,8 +66,8 @@ private void AddNodes(FolderState folder) if (NodeCount > 0) { - Logger.Information($"Creating {NodeCount} GUID node(s) of type: {NodeType}"); - Logger.Information($"Node values will change every {NodeRate} ms"); + Logger.LogInformation($"Creating {NodeCount} GUID node(s) of type: {NodeType}"); + Logger.LogInformation($"Node values will change every {NodeRate} ms"); } for (int i = 0; i < NodeCount; i++) diff --git a/src/PluginNodes/DipPluginNode.cs b/src/PluginNodes/DipPluginNode.cs index 3c63f0d5..56bb4cfc 100644 --- a/src/PluginNodes/DipPluginNode.cs +++ b/src/PluginNodes/DipPluginNode.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -52,7 +53,7 @@ public void StartSimulation() { _dipCycleInPhase = PlcSimulation.SimulationCycleCount; _dipAnomalyCycle = _random.Next(PlcSimulation.SimulationCycleCount); - Logger.Verbose($"First dip anomaly cycle: {_dipAnomalyCycle}"); + Logger.LogTrace($"First dip anomaly cycle: {_dipAnomalyCycle}"); _node.Start(DipGenerator, PlcSimulation.SimulationCycleLength); } @@ -98,20 +99,20 @@ private double DipGenerator(double value) if (_isEnabled && _dipCycleInPhase == _dipAnomalyCycle) { nextValue = SimulationMaxAmplitude * -10; - Logger.Verbose("Generate dip anomaly"); + Logger.LogTrace("Generate dip anomaly"); } else { nextValue = SimulationMaxAmplitude * Math.Sin(((2 * Math.PI) / PlcSimulation.SimulationCycleCount) * _dipCycleInPhase); } - Logger.Verbose($"Spike cycle: {_dipCycleInPhase} data: {nextValue}"); + Logger.LogTrace($"Spike cycle: {_dipCycleInPhase} data: {nextValue}"); // end of cycle: reset cycle count and calc next anomaly cycle if (--_dipCycleInPhase == 0) { _dipCycleInPhase = PlcSimulation.SimulationCycleCount; _dipAnomalyCycle = _random.Next(PlcSimulation.SimulationCycleCount); - Logger.Verbose($"Next dip anomaly cycle: {_dipAnomalyCycle}"); + Logger.LogTrace($"Next dip anomaly cycle: {_dipAnomalyCycle}"); } return nextValue; diff --git a/src/PluginNodes/FastPluginNodes.cs b/src/PluginNodes/FastPluginNodes.cs index d419694a..fce4df75 100644 --- a/src/PluginNodes/FastPluginNodes.cs +++ b/src/PluginNodes/FastPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.PluginNodes.Models; using System; @@ -194,7 +195,7 @@ private void SetStartUpdateFastNodesProperties(ref MethodState method) private ServiceResult OnStopUpdateFastNodes(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _updateNodes = false; - Logger.Debug("StopUpdateFastNodes method called"); + Logger.LogDebug("StopUpdateFastNodes method called"); return ServiceResult.Good; } @@ -204,7 +205,7 @@ private ServiceResult OnStopUpdateFastNodes(ISystemContext context, MethodState private ServiceResult OnStartUpdateFastNodes(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _updateNodes = true; - Logger.Debug("StartUpdateFastNodes method called"); + Logger.LogDebug("StartUpdateFastNodes method called"); return ServiceResult.Good; } diff --git a/src/PluginNodes/NegTrendPluginNode.cs b/src/PluginNodes/NegTrendPluginNode.cs index e948e2d9..1f8f0d39 100644 --- a/src/PluginNodes/NegTrendPluginNode.cs +++ b/src/PluginNodes/NegTrendPluginNode.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -54,7 +55,7 @@ public void StartSimulation() { _negTrendAnomalyPhase = _random.Next(10); _negTrendCycleInPhase = PlcSimulation.SimulationCycleCount; - Logger.Verbose($"First neg trend anomaly phase: {_negTrendAnomalyPhase}"); + Logger.LogTrace($"First neg trend anomaly phase: {_negTrendAnomalyPhase}"); _node.Start(NegTrendGenerator, PlcSimulation.SimulationCycleLength); } @@ -112,7 +113,7 @@ private double NegTrendGenerator(double value) if (_isEnabled && _negTrendPhase >= _negTrendAnomalyPhase) { nextValue = TREND_BASEVALUE - ((_negTrendPhase - _negTrendAnomalyPhase) / 10d); - Logger.Verbose("Generate negtrend anomaly"); + Logger.LogTrace("Generate negtrend anomaly"); } // end of cycle: reset cycle count and calc next anomaly cycle @@ -120,7 +121,7 @@ private double NegTrendGenerator(double value) { _negTrendCycleInPhase = PlcSimulation.SimulationCycleCount; _negTrendPhase++; - Logger.Verbose($"Neg trend phase: {_negTrendPhase}, data: {nextValue}"); + Logger.LogTrace($"Neg trend phase: {_negTrendPhase}, data: {nextValue}"); } return nextValue; @@ -137,7 +138,7 @@ private void SetResetTrendMethodProperties(ref MethodState method) private ServiceResult OnResetTrendCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { ResetTrendData(); - Logger.Debug("ResetNegTrend method called"); + Logger.LogDebug("ResetNegTrend method called"); return ServiceResult.Good; } diff --git a/src/PluginNodes/NodeSet2PluginNodes.cs b/src/PluginNodes/NodeSet2PluginNodes.cs index 17b419f3..ab53bdae 100644 --- a/src/PluginNodes/NodeSet2PluginNodes.cs +++ b/src/PluginNodes/NodeSet2PluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -59,11 +60,11 @@ private void AddNodes(FolderState folder) } catch (Exception e) { - Logger.Error(e, "Error loading NodeSet2 file {file}: {error}", file, e.Message); + Logger.LogError(e, "Error loading NodeSet2 file {file}: {error}", file, e.Message); } } - Logger.Information("Completed processing NodeSet2 file(s)"); + Logger.LogInformation("Completed processing NodeSet2 file(s)"); } /// diff --git a/src/PluginNodes/PosTrendPluginNode.cs b/src/PluginNodes/PosTrendPluginNode.cs index b7a44cce..d18fb09c 100644 --- a/src/PluginNodes/PosTrendPluginNode.cs +++ b/src/PluginNodes/PosTrendPluginNode.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -54,7 +55,7 @@ public void StartSimulation() { _posTrendAnomalyPhase = _random.Next(10); _posTrendCycleInPhase = PlcSimulation.SimulationCycleCount; - Logger.Verbose($"First pos trend anomaly phase: {_posTrendAnomalyPhase}"); + Logger.LogTrace($"First pos trend anomaly phase: {_posTrendAnomalyPhase}"); _node.Start(PosTrendGenerator, PlcSimulation.SimulationCycleLength); } @@ -112,7 +113,7 @@ private double PosTrendGenerator(double value) if (_isEnabled && _posTrendPhase >= _posTrendAnomalyPhase) { nextValue = TREND_BASEVALUE + ((_posTrendPhase - _posTrendAnomalyPhase) / 10d); - Logger.Verbose("Generate postrend anomaly"); + Logger.LogTrace("Generate postrend anomaly"); } // end of cycle: reset cycle count and calc next anomaly cycle @@ -120,7 +121,7 @@ private double PosTrendGenerator(double value) { _posTrendCycleInPhase = PlcSimulation.SimulationCycleCount; _posTrendPhase++; - Logger.Verbose($"Pos trend phase: {_posTrendPhase}, data: {nextValue}"); + Logger.LogTrace($"Pos trend phase: {_posTrendPhase}, data: {nextValue}"); } return nextValue; @@ -137,7 +138,7 @@ private void SetResetTrendMethodProperties(ref MethodState method) private ServiceResult OnResetTrendCall(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { ResetTrendData(); - Logger.Debug("ResetPosTrend method called"); + Logger.LogDebug("ResetPosTrend method called"); return ServiceResult.Good; } diff --git a/src/PluginNodes/SlowFastCommon.cs b/src/PluginNodes/SlowFastCommon.cs index 11f948fa..56e609c1 100644 --- a/src/PluginNodes/SlowFastCommon.cs +++ b/src/PluginNodes/SlowFastCommon.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using System; using static OpcPlc.Program; @@ -37,9 +38,9 @@ private BaseDataVariableState[] CreateBaseLoadNodes(FolderState folder, string n if (count > 0) { - Logger.Information($"Creating {count} {name} nodes of type: {type}"); - Logger.Information("Node values will change every " + nodeRate + " ms"); - Logger.Information("Node values sampling rate is " + nodeSamplingInterval + " ms"); + Logger.LogInformation($"Creating {count} {name} nodes of type: {type}"); + Logger.LogInformation("Node values will change every " + nodeRate + " ms"); + Logger.LogInformation("Node values sampling rate is " + nodeSamplingInterval + " ms"); } for (int i = 0; i < count; i++) @@ -135,7 +136,7 @@ private void UpdateNodes(BaseDataVariableState[] nodes, NodeType type, StatusCod { if (nodes == null || nodes.Length == 0) { - Logger.Warning("Invalid argument {argument} provided.", nodes); + Logger.LogWarning("Invalid argument {argument} provided.", nodes); return; } diff --git a/src/PluginNodes/SlowPluginNodes.cs b/src/PluginNodes/SlowPluginNodes.cs index 1f436886..4badb209 100644 --- a/src/PluginNodes/SlowPluginNodes.cs +++ b/src/PluginNodes/SlowPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.PluginNodes.Models; using System; @@ -184,7 +185,7 @@ private void SetStartUpdateSlowNodesProperties(ref MethodState method) private ServiceResult OnStopUpdateSlowNodes(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _updateNodes = false; - Logger.Debug("StopUpdateSlowNodes method called"); + Logger.LogDebug("StopUpdateSlowNodes method called"); return ServiceResult.Good; } @@ -194,7 +195,7 @@ private ServiceResult OnStopUpdateSlowNodes(ISystemContext context, MethodState private ServiceResult OnStartUpdateSlowNodes(ISystemContext context, MethodState method, IList inputArguments, IList outputArguments) { _updateNodes = true; - Logger.Debug("StartUpdateSlowNodes method called"); + Logger.LogDebug("StartUpdateSlowNodes method called"); return ServiceResult.Good; } diff --git a/src/PluginNodes/SpikePluginNode.cs b/src/PluginNodes/SpikePluginNode.cs index e9836f2e..92d8f053 100644 --- a/src/PluginNodes/SpikePluginNode.cs +++ b/src/PluginNodes/SpikePluginNode.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -52,7 +53,7 @@ public void StartSimulation() { _spikeCycleInPhase = PlcSimulation.SimulationCycleCount; _spikeAnomalyCycle = _random.Next(PlcSimulation.SimulationCycleCount); - Logger.Verbose($"First spike anomaly cycle: {_spikeAnomalyCycle}"); + Logger.LogTrace($"First spike anomaly cycle: {_spikeAnomalyCycle}"); _node.Start(SpikeGenerator, PlcSimulation.SimulationCycleLength); } @@ -99,20 +100,20 @@ private double SpikeGenerator(double value) { // todo calculate nextValue = SimulationMaxAmplitude * 10; - Logger.Verbose("Generate spike anomaly"); + Logger.LogTrace("Generate spike anomaly"); } else { nextValue = SimulationMaxAmplitude * Math.Sin(((2 * Math.PI) / PlcSimulation.SimulationCycleCount) * _spikeCycleInPhase); } - Logger.Verbose($"Spike cycle: {_spikeCycleInPhase} data: {nextValue}"); + Logger.LogTrace($"Spike cycle: {_spikeCycleInPhase} data: {nextValue}"); // end of cycle: reset cycle count and calc next anomaly cycle if (--_spikeCycleInPhase == 0) { _spikeCycleInPhase = PlcSimulation.SimulationCycleCount; _spikeAnomalyCycle = _random.Next(PlcSimulation.SimulationCycleCount); - Logger.Verbose($"Next spike anomaly cycle: {_spikeAnomalyCycle}"); + Logger.LogTrace($"Next spike anomaly cycle: {_spikeAnomalyCycle}"); } return nextValue; diff --git a/src/PluginNodes/UaNodesPluginNodes.cs b/src/PluginNodes/UaNodesPluginNodes.cs index d6de02c4..b0145a05 100644 --- a/src/PluginNodes/UaNodesPluginNodes.cs +++ b/src/PluginNodes/UaNodesPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Helpers; using OpcPlc.PluginNodes.Models; @@ -63,11 +64,11 @@ private void AddNodes(FolderState folder) } catch (Exception e) { - Logger.Error(e, "Error loading binary uanodes file {file}: {error}", file, e.Message); + Logger.LogError(e, "Error loading binary uanodes file {file}: {error}", file, e.Message); } } - Logger.Information("Completed processing binary uanodes file(s)"); + Logger.LogInformation("Completed processing binary uanodes file(s)"); } /// diff --git a/src/PluginNodes/UserDefinedPluginNodes.cs b/src/PluginNodes/UserDefinedPluginNodes.cs index b0a202b1..dd1961b8 100644 --- a/src/PluginNodes/UserDefinedPluginNodes.cs +++ b/src/PluginNodes/UserDefinedPluginNodes.cs @@ -1,5 +1,6 @@ namespace OpcPlc.PluginNodes; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Opc.Ua; using OpcPlc.Helpers; @@ -59,22 +60,22 @@ private void AddNodes(FolderState folder) TypeNameHandling = TypeNameHandling.All, }); - Logger.Information($"Processing node information configured in {_nodesFileName}"); + Logger.LogInformation($"Processing node information configured in {_nodesFileName}"); Nodes = AddNodes(folder, cfgFolder).ToList(); } catch (Exception e) { - Logger.Error(e, "Error loading user defined node file {file}: {error}", _nodesFileName, e.Message); + Logger.LogError(e, "Error loading user defined node file {file}: {error}", _nodesFileName, e.Message); } - Logger.Information("Completed processing user defined node file"); + Logger.LogInformation("Completed processing user defined node file"); } private IEnumerable AddNodes(FolderState folder, ConfigFolder cfgFolder) { - Logger.Debug($"Create folder {cfgFolder.Folder}"); + Logger.LogDebug($"Create folder {cfgFolder.Folder}"); FolderState userNodesFolder = _plcNodeManager.CreateFolder( folder, path: cfgFolder.Folder, @@ -88,7 +89,7 @@ private IEnumerable AddNodes(FolderState folder, ConfigFolder if (!isDecimal && !isString) { - Logger.Error($"The type of the node configuration for node with name {node.Name} ({node.NodeId.GetType()}) is not supported. Only decimal, string, and guid are supported. Defaulting to string."); + Logger.LogError($"The type of the node configuration for node with name {node.Name} ({node.NodeId.GetType()}) is not supported. Only decimal, string, and guid are supported. Defaulting to string."); node.NodeId = node.NodeId.ToString(); } @@ -115,10 +116,10 @@ private IEnumerable AddNodes(FolderState folder, ConfigFolder node.Description = node.Name; } - Logger.Debug("Create node with Id {typedNodeId}, BrowseName {name} and type {type} in namespace with index {namespaceIndex}", + Logger.LogDebug("Create node with Id {typedNodeId}, BrowseName {name} and type {type} in namespace with index {namespaceIndex}", typedNodeId, node.Name, - node.NodeId.GetType(), + (string)node.NodeId.GetType().Name, _plcNodeManager.NamespaceIndexes[(int)NamespaceType.OpcPlcApplications]); CreateBaseVariable(userNodesFolder, node); @@ -159,7 +160,7 @@ public void CreateBaseVariable(NodeState parent, ConfigNode node) { if (!Enum.TryParse(node.DataType, out BuiltInType nodeDataType)) { - Logger.Error($"Value '{node.DataType}' of node '{node.NodeId}' cannot be parsed. Defaulting to 'Int32'"); + Logger.LogError($"Value '{node.DataType}' of node '{node.NodeId}' cannot be parsed. Defaulting to 'Int32'"); node.DataType = "Int32"; } @@ -171,7 +172,7 @@ public void CreateBaseVariable(NodeState parent, ConfigNode node) } catch { - Logger.Error($"AccessLevel '{node.AccessLevel}' of node '{node.Name}' is not supported. Defaulting to 'CurrentReadOrWrite'"); + Logger.LogError($"AccessLevel '{node.AccessLevel}' of node '{node.Name}' is not supported. Defaulting to 'CurrentReadOrWrite'"); node.AccessLevel = "CurrentRead"; accessLevel = AccessLevels.CurrentReadOrWrite; } diff --git a/src/Program.cs b/src/Program.cs index 942a3827..10c19f19 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,15 +1,17 @@ -namespace OpcPlc; +namespace OpcPlc; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Opc.Ua; using OpcPlc.Extensions; using OpcPlc.Helpers; +using OpcPlc.Logging; using OpcPlc.PluginNodes.Models; -using Serilog; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Data; using System.Diagnostics; using System.IO; using System.Linq; @@ -27,10 +29,15 @@ public static class Program /// public const string ProgramName = "OpcPlc"; + /// + /// The LoggerFactory used to create logging objects. + /// + public static ILoggerFactory LoggerFactory = null; + /// /// Logging object. /// - public static Serilog.Core.Logger Logger = null; + public static ILogger Logger = null; /// /// Nodes to extend the address space. @@ -109,7 +116,7 @@ public static class Program /// Logging configuration. /// public static string LogFileName = $"{Dns.GetHostName().Split('.')[0].ToLowerInvariant()}-plc.log"; - public static string LogLevel = "info"; + public static string LogLevelCli = "info"; public static TimeSpan LogFileFlushTimeSpanSec = TimeSpan.FromSeconds(30); public enum NodeType @@ -153,24 +160,24 @@ public static async Task MainAsync(string[] args, CancellationToken cancellation // Validate and parse extra arguments if (extraArgs.Count > 0) { - Logger.Warning($"Found one or more invalid command line arguments: {string.Join(" ", extraArgs)}"); + Logger.LogWarning($"Found one or more invalid command line arguments: {string.Join(" ", extraArgs)}"); CliOptions.PrintUsage(options); } LogLogo(); - Logger.Information("Current directory: {currentDirectory}", Directory.GetCurrentDirectory()); - Logger.Information("Log file: {logFileName}", Path.GetFullPath(LogFileName)); - Logger.Information("Log level: {logLevel}", LogLevel); + Logger.LogInformation("Current directory: {currentDirectory}", Directory.GetCurrentDirectory()); + Logger.LogInformation("Log file: {logFileName}", Path.GetFullPath(LogFileName)); + Logger.LogInformation("Log level: {logLevel}", LogLevelCli); // Show version. var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); - Logger.Information("{ProgramName} {version} starting up ...", + Logger.LogInformation("{ProgramName} {version} starting up ...", ProgramName, $"v{fileVersion.ProductMajorPart}.{fileVersion.ProductMinorPart}.{fileVersion.ProductBuildPart} (SDK {Utils.GetAssemblyBuildNumber()})"); - Logger.Debug("Informational version: {version}", + Logger.LogDebug("Informational version: {version}", $"v{(Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute)?.InformationalVersion} (SDK {Utils.GetAssemblySoftwareVersion()} from {Utils.GetAssemblyTimestamp()})"); - Logger.Debug("Build date: {date}", + Logger.LogDebug("Build date: {date}", $"{File.GetCreationTime(Assembly.GetExecutingAssembly().Location)}"); using var host = CreateHostBuilder(args); @@ -185,10 +192,10 @@ public static async Task MainAsync(string[] args, CancellationToken cancellation } catch (Exception ex) { - Logger.Fatal(ex, "OPC UA server failed unexpectedly"); + Logger.LogCritical(ex, "OPC UA server failed unexpectedly"); } - Logger.Information("OPC UA server exiting..."); + Logger.LogInformation("OPC UA server exiting..."); } /// @@ -218,20 +225,20 @@ private static void StartWebServer(IHost host) if (ShowPublisherConfigJsonIp) { - Logger.Information("Web server started: {pnJsonUri}", $"http://{GetIpAddress()}:{WebServerPort}/{PnJson}"); + Logger.LogInformation("Web server started: {pnJsonUri}", $"http://{GetIpAddress()}:{WebServerPort}/{PnJson}"); } else if (ShowPublisherConfigJsonPh) { - Logger.Information("Web server started: {pnJsonUri}", $"http://{Hostname}:{WebServerPort}/{PnJson}"); + Logger.LogInformation("Web server started: {pnJsonUri}", $"http://{Hostname}:{WebServerPort}/{PnJson}"); } else { - Logger.Information("Web server started on port {webServerPort}", WebServerPort); + Logger.LogInformation("Web server started on port {webServerPort}", WebServerPort); } } catch (Exception e) { - Logger.Error("Could not start web server on port {webServerPort}: {message}", + Logger.LogError("Could not start web server on port {webServerPort}: {message}", WebServerPort, e.Message); } @@ -268,27 +275,27 @@ private static async Task ConsoleServerAsync(CancellationToken cancellationToken ApplicationConfiguration plcApplicationConfiguration = await plcOpcApplicationConfiguration.ConfigureAsync().ConfigureAwait(false); // start the server. - Logger.Information("Starting server on endpoint {endpoint} ...", plcApplicationConfiguration.ServerConfiguration.BaseAddresses[0]); - Logger.Information("Simulation settings are:"); - Logger.Information("One simulation phase consists of {SimulationCycleCount} cycles", SimulationCycleCount); - Logger.Information("One cycle takes {SimulationCycleLength} ms", SimulationCycleLength); - Logger.Information("Reference test simulation: {addReferenceTestSimulation}", + Logger.LogInformation("Starting server on endpoint {endpoint} ...", plcApplicationConfiguration.ServerConfiguration.BaseAddresses[0]); + Logger.LogInformation("Simulation settings are:"); + Logger.LogInformation("One simulation phase consists of {SimulationCycleCount} cycles", SimulationCycleCount); + Logger.LogInformation("One cycle takes {SimulationCycleLength} ms", SimulationCycleLength); + Logger.LogInformation("Reference test simulation: {addReferenceTestSimulation}", AddReferenceTestSimulation ? "Enabled" : "Disabled"); - Logger.Information("Simple events: {addSimpleEventsSimulation}", + Logger.LogInformation("Simple events: {addSimpleEventsSimulation}", AddSimpleEventsSimulation ? "Enabled" : "Disabled"); - Logger.Information("Alarms: {addAlarmSimulation}", AddAlarmSimulation ? "Enabled" : "Disabled"); - Logger.Information("Deterministic alarms: {deterministicAlarmSimulation}", + Logger.LogInformation("Alarms: {addAlarmSimulation}", AddAlarmSimulation ? "Enabled" : "Disabled"); + Logger.LogInformation("Deterministic alarms: {deterministicAlarmSimulation}", DeterministicAlarmSimulationFile != null ? "Enabled" : "Disabled"); - Logger.Information("Anonymous authentication: {anonymousAuth}", DisableAnonymousAuth ? "Disabled" : "Enabled"); - Logger.Information("Reject chain validation with CA certs with unknown revocation status: {rejectValidationUnknownRevocStatus}", DontRejectUnknownRevocationStatus ? "Disabled" : "Enabled"); - Logger.Information("Username/Password authentication: {usernamePasswordAuth}", DisableUsernamePasswordAuth ? "Disabled" : "Enabled"); - Logger.Information("Certificate authentication: {certAuth}", DisableCertAuth ? "Disabled" : "Enabled"); + Logger.LogInformation("Anonymous authentication: {anonymousAuth}", DisableAnonymousAuth ? "Disabled" : "Enabled"); + Logger.LogInformation("Reject chain validation with CA certs with unknown revocation status: {rejectValidationUnknownRevocStatus}", DontRejectUnknownRevocationStatus ? "Disabled" : "Enabled"); + Logger.LogInformation("Username/Password authentication: {usernamePasswordAuth}", DisableUsernamePasswordAuth ? "Disabled" : "Enabled"); + Logger.LogInformation("Certificate authentication: {certAuth}", DisableCertAuth ? "Disabled" : "Enabled"); // Add simple events, alarms, reference test simulation and deterministic alarms. PlcServer = new PlcServer(TimeService); PlcServer.Start(plcApplicationConfiguration); - Logger.Information("OPC UA Server started"); + Logger.LogInformation("OPC UA Server started"); // Add remaining base simulations. PlcSimulation = new PlcSimulation(PlcServer); @@ -312,12 +319,11 @@ await PnJsonHelper.PrintPublisherConfigJsonAsync( } Ready = true; - Logger.Information("PLC simulation started, press Ctrl+C to exit ..."); + Logger.LogInformation("PLC simulation started, press Ctrl+C to exit ..."); // Wait for Ctrl-C to allow canceling the connection process. var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - Console.CancelKeyPress += (_, eArgs) => - { + Console.CancelKeyPress += (_, eArgs) => { cancellationTokenSource.Cancel(); eArgs.Cancel = true; }; @@ -332,66 +338,55 @@ await PnJsonHelper.PrintPublisherConfigJsonAsync( /// Initialize logging. /// private static void InitLogging() - { - var loggerConfiguration = new LoggerConfiguration(); + { + if (LoggerFactory != null && Logger != null) + { + return; + } + + LogLevel logLevel = LogLevel.Information; // set the log level - switch (LogLevel) + switch (LogLevelCli) { - case "fatal": - loggerConfiguration.MinimumLevel.Fatal(); - OpcStackTraceMask = OpcTraceToLoggerFatal = 0; + case "critical": + logLevel = LogLevel.Critical; break; case "error": - loggerConfiguration.MinimumLevel.Error(); - OpcStackTraceMask = OpcTraceToLoggerError = Utils.TraceMasks.Error; + logLevel = LogLevel.Error; break; case "warn": - loggerConfiguration.MinimumLevel.Warning(); - OpcStackTraceMask = OpcTraceToLoggerError = Utils.TraceMasks.Error | Utils.TraceMasks.StackTrace; - OpcTraceToLoggerWarning = Utils.TraceMasks.StackTrace; - OpcStackTraceMask |= OpcTraceToLoggerWarning; + logLevel = LogLevel.Warning; break; case "info": - loggerConfiguration.MinimumLevel.Information(); - OpcTraceToLoggerError = Utils.TraceMasks.Error; - OpcTraceToLoggerWarning = Utils.TraceMasks.StackTrace; - OpcTraceToLoggerInformation = Utils.TraceMasks.Security; - OpcStackTraceMask = OpcTraceToLoggerError | OpcTraceToLoggerInformation | OpcTraceToLoggerWarning; + logLevel = LogLevel.Information; break; case "debug": - loggerConfiguration.MinimumLevel.Debug(); - OpcTraceToLoggerError = Utils.TraceMasks.Error; - OpcTraceToLoggerWarning = Utils.TraceMasks.StackTrace; - OpcTraceToLoggerInformation = Utils.TraceMasks.Security; - OpcTraceToLoggerDebug = Utils.TraceMasks.Operation | Utils.TraceMasks.StartStop | Utils.TraceMasks.ExternalSystem; - OpcStackTraceMask = OpcTraceToLoggerError | OpcTraceToLoggerInformation | OpcTraceToLoggerDebug | OpcTraceToLoggerWarning; - break; - case "verbose": - loggerConfiguration.MinimumLevel.Verbose(); - OpcTraceToLoggerError = Utils.TraceMasks.Error | Utils.TraceMasks.StackTrace; - OpcTraceToLoggerInformation = Utils.TraceMasks.Security; - OpcStackTraceMask = OpcTraceToLoggerVerbose = Utils.TraceMasks.All; + logLevel = LogLevel.Debug; break; + case "trace": + logLevel = LogLevel.Trace; + break; + default: + throw new ArgumentOutOfRangeException(nameof(LogLevelCli), $"Unknown log level: {LogLevelCli}"); } - // set logging sinks - loggerConfiguration.WriteTo.Console(); - + LoggerFactory = LoggingProvider.CreateDefaultLoggerFactory(logLevel); + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP"))) { LogFileName = Environment.GetEnvironmentVariable("_GW_LOGP"); - } - - if (!string.IsNullOrEmpty(LogFileName)) - { - // configure rolling file sink - const int MAX_LOGFILE_SIZE = 1024 * 1024; - const int MAX_RETAINED_LOGFILES = 2; - loggerConfiguration.WriteTo.File(LogFileName, fileSizeLimitBytes: MAX_LOGFILE_SIZE, flushToDiskInterval: LogFileFlushTimeSpanSec, rollOnFileSizeLimit: true, retainedFileCountLimit: MAX_RETAINED_LOGFILES); - } - - Logger = loggerConfiguration.CreateLogger(); + } + + if (!string.IsNullOrEmpty(LogFileName)) + { + // configure rolling file sink + const int MAX_LOGFILE_SIZE = 1024 * 1024; + const int MAX_RETAINED_LOGFILES = 2; + LoggerFactory.AddFile(LogFileName, logLevel, null, false, fileSizeLimitBytes: MAX_LOGFILE_SIZE, retainedFileCountLimit: MAX_RETAINED_LOGFILES); + } + + Logger = LoggerFactory.CreateLogger("opcPlc"); } /// @@ -400,10 +395,9 @@ private static void InitLogging() public static IHost CreateHostBuilder(string[] args) { var host = Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseContentRoot(Directory.GetCurrentDirectory()); // Avoid System.InvalidOperationException. - webBuilder.UseUrls($"http://*:{WebServerPort}"); + .ConfigureWebHostDefaults(webBuilder => { + webBuilder.UseContentRoot(Directory.GetCurrentDirectory()); // Avoid System.InvalidOperationException. + webBuilder.UseUrls($"http://*:{WebServerPort}"); webBuilder.UseStartup(); }).Build(); @@ -412,7 +406,7 @@ public static IHost CreateHostBuilder(string[] args) private static void LogLogo() { - Logger.Information( + Logger.LogInformation( @" ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██████╗ ██╔═══██╗██╔══██╗██╔════╝ ██╔══██╗██║ ██╔════╝ diff --git a/src/Reference/ReferenceNodeManager.cs b/src/Reference/ReferenceNodeManager.cs index 7b8c3cae..b78d07b7 100644 --- a/src/Reference/ReferenceNodeManager.cs +++ b/src/Reference/ReferenceNodeManager.cs @@ -1,4 +1,4 @@ -/* ======================================================================== +/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 @@ -1767,7 +1767,7 @@ private AnalogItemState CreateAnalogItemVariable(NodeState parent, string path, /// /// Creates a new variable. /// - private DataItemState CreateTwoStateDiscreteItemVariable(NodeState parent, string path, string name, string trueState, string falseState) + private TwoStateDiscreteState CreateTwoStateDiscreteItemVariable(NodeState parent, string path, string name, string trueState, string falseState) { TwoStateDiscreteState variable = new TwoStateDiscreteState(parent); @@ -1814,7 +1814,7 @@ private DataItemState CreateTwoStateDiscreteItemVariable(NodeState parent, strin /// /// Creates a new variable. /// - private DataItemState CreateMultiStateDiscreteItemVariable(NodeState parent, string path, string name, params string[] values) + private MultiStateDiscreteState CreateMultiStateDiscreteItemVariable(NodeState parent, string path, string name, params string[] values) { MultiStateDiscreteState variable = new MultiStateDiscreteState(parent); @@ -1865,7 +1865,7 @@ private DataItemState CreateMultiStateDiscreteItemVariable(NodeState parent, str /// /// Creates a new UInt32 variable. /// - private DataItemState CreateMultiStateValueDiscreteItemVariable(NodeState parent, string path, string name, params string[] enumNames) + private MultiStateValueDiscreteState CreateMultiStateValueDiscreteItemVariable(NodeState parent, string path, string name, params string[] enumNames) { return CreateMultiStateValueDiscreteItemVariable(parent, path, name, null, enumNames); } @@ -1873,7 +1873,7 @@ private DataItemState CreateMultiStateValueDiscreteItemVariable(NodeState parent /// /// Creates a new variable. /// - private DataItemState CreateMultiStateValueDiscreteItemVariable(NodeState parent, string path, string name, NodeId nodeId, params string[] enumNames) + private MultiStateValueDiscreteState CreateMultiStateValueDiscreteItemVariable(NodeState parent, string path, string name, NodeId nodeId, params string[] enumNames) { MultiStateValueDiscreteState variable = new MultiStateValueDiscreteState(parent); @@ -2236,7 +2236,7 @@ private BaseDataVariableState[] CreateDynamicVariables(NodeState parent, string /// /// Creates a new variable type. /// - private BaseVariableTypeState CreateVariableType(NodeState parent, IDictionary> externalReferences, string path, string name, BuiltInType dataType, int valueRank) + private BaseDataVariableTypeState CreateVariableType(NodeState parent, IDictionary> externalReferences, string path, string name, BuiltInType dataType, int valueRank) { BaseDataVariableTypeState type = new BaseDataVariableTypeState(); diff --git a/src/UserAuthentication.cs b/src/UserAuthentication.cs index 287d6568..2f75129f 100644 --- a/src/UserAuthentication.cs +++ b/src/UserAuthentication.cs @@ -1,5 +1,6 @@ namespace OpcPlc; +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Server; using System; @@ -97,7 +98,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg if (args.NewIdentity is UserNameIdentityToken userNameToken) { args.Identity = VerifyPassword(userNameToken); - Logger.Information("UserName Token Accepted: {displayName}", args.Identity.DisplayName); + Logger.LogInformation("UserName Token Accepted: {displayName}", args.Identity.DisplayName); return; } @@ -106,7 +107,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg { VerifyCertificate(x509Token.Certificate); args.Identity = new UserIdentity(x509Token); - Logger.Information("X509 Token Accepted: {displayName}", args.Identity.DisplayName); + Logger.LogInformation("X509 Token Accepted: {displayName}", args.Identity.DisplayName); return; } } diff --git a/src/opc-plc.csproj b/src/opc-plc.csproj index c10c3e8d..35b07d0e 100644 --- a/src/opc-plc.csproj +++ b/src/opc-plc.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -17,25 +17,29 @@ - - - - - - - - + - - - - + + + + + + + + + + + + + + + diff --git a/tests/MonitoringTestsBase.cs b/tests/MonitoringTestsBase.cs index 2685f460..ec3ff12a 100644 --- a/tests/MonitoringTestsBase.cs +++ b/tests/MonitoringTestsBase.cs @@ -4,6 +4,7 @@ namespace OpcPlc.Tests; using NUnit.Framework; using Opc.Ua; using Opc.Ua.Client; +using OpcPlc.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -37,6 +38,7 @@ protected SubscriptionTestsBase(string[] args = default) : base(args) [SetUp] public void CreateSubscription() { + Utils.SetLogger(new TestLogger(TestContext.Out, new SyslogFormatter(new SyslogFormatterOptions()))); _subscription = Session.DefaultSubscription; Session.AddSubscription(_subscription); _subscription.Create(); diff --git a/tests/PlcSimulatorFixture.cs b/tests/PlcSimulatorFixture.cs index 3a5ec571..cfc2946a 100644 --- a/tests/PlcSimulatorFixture.cs +++ b/tests/PlcSimulatorFixture.cs @@ -1,13 +1,14 @@ namespace OpcPlc.Tests; using FluentAssertions; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Opc.Ua; using Opc.Ua.Client; using Opc.Ua.Configuration; using OpcPlc; -using Serilog; +using OpcPlc.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -79,9 +80,10 @@ public PlcSimulatorFixture(string[] args) public async Task Start() { Reset(); - Program.Logger = new LoggerConfiguration() - .WriteTo.NUnitOutput() - .CreateLogger(); + + Program.LoggerFactory = LoggingProvider.CreateDefaultLoggerFactory(LogLevel.Information); + Program.Logger = new TestLogger(TestContext.Progress, new SyslogFormatter(new SyslogFormatterOptions())); + _log = TestContext.Progress; var mock = new Mock(); diff --git a/tests/TestLogger.cs b/tests/TestLogger.cs new file mode 100644 index 00000000..a99c7243 --- /dev/null +++ b/tests/TestLogger.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace OpcPlc.Tests; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Logging.Console; +using System; +using System.IO; + +public class TestLogger : ILogger +{ + private readonly TextWriter _outputWriter; + private readonly ConsoleFormatter _formatter; + private readonly string _category = typeof(T).FullName; + + public TestLogger(TextWriter outputWriter, ConsoleFormatter formatter) + { + _outputWriter = outputWriter; + _formatter = formatter; + } + + public LogLevel MinimumLogLevel { get; set; } = LogLevel.Debug; + + public IDisposable BeginScope(TState state) + where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel >= MinimumLogLevel; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (logLevel < MinimumLogLevel) + { + return; + } + + _formatter.Write(new LogEntry(logLevel, _category, eventId, state, exception, formatter), null, _outputWriter); + } +} diff --git a/tests/opc-plc-tests.csproj b/tests/opc-plc-tests.csproj index 338a25ae..0c75245b 100644 --- a/tests/opc-plc-tests.csproj +++ b/tests/opc-plc-tests.csproj @@ -13,14 +13,24 @@ - - - - - - + + + + + + + + + + + + + + + + diff --git a/version.json b/version.json index fd939be7..8da508ca 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.9.13", + "version": "2.9.14", "versionHeightOffset": -1, "publicReleaseRefSpec": [ "^refs/heads/main$",