diff --git a/.Zeugwerk/config.json b/.Zeugwerk/config.json new file mode 100644 index 0000000..6e4ee7a --- /dev/null +++ b/.Zeugwerk/config.json @@ -0,0 +1,24 @@ +{ + "fileversion": 1, + "solution": "TcLog.sln", + "projects": [ + { + "name": "TcLogProj", + "plcs": [ + { + "version": "0.2.2", + "name": "TcLog", + "type": "Library", + "frameworks": {}, + "references": {}, + "repositories": [], + "bindings": {}, + "patches": { + "platform": {}, + "argument": {} + } + } + ] + } + ] +} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..189e57a --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,26 @@ +name: Documentation +on: + push: + branches: + - main + paths: + - 'docs/**' + - 'TcLog/**' + pull_request: + workflow_dispatch: +jobs: + Build: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Build + uses: Zeugwerk/zkdoc-action@1.0.0 + with: + username: ${{ secrets.ACTIONS_ZGWK_USERNAME }} + password: ${{ secrets.ACTIONS_ZGWK_PASSWORD }} + filepath: "." + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} + publish_dir: archive/documentation/html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eeb59fa..f94de91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,12 +39,8 @@ These tests are complemented by [XUnit](https://xunit.net/) tests in the .NET pr The project uses the [TcBlack](https://github.com/Roald87/TcBlack) formatter for TwinCAT code. Please make sure to format your code accordingly before creating a pull request. -The .NET code is formatted using the default Visual Studio formatter. - #### Naming conventions This project uses [Zeugwerk naming conventions](https://doc.zeugwerk.dev/contribute/contribute_code.html#naming-conventions) for the TwinCAT code. -Unfortunately, the Beckhoff PLC Static Analysis does not support checking custom naming conventions yet. Therefore, please make sure to check your code manually before creating a pull request. - -The .NET code follows the default .NET naming conventions. \ No newline at end of file +Unfortunately, the Beckhoff PLC Static Analysis does not support checking custom naming conventions yet. Therefore, please make sure to check your code manually before creating a pull request. \ No newline at end of file diff --git a/README.md b/README.md index 024c370..66be266 100644 --- a/README.md +++ b/README.md @@ -1,325 +1,68 @@ -![](TcLog_header.svg "TcLog_header") +![](docs/images/TcLog_header.svg "TcLog_header") *Logging in TwinCAT with the on-board means is limited to the output as ADS event. The TcLog library presented here enables flexible logging to the file system.* -This readme is a copy of the [blog post introducing this framework](https://benediktgeisler.de/en/blog/tclog/). - -## Logging in TwinCAT -From time to time it happens that I need a log function in TwinCAT to find sporadic errors or to record user interactions. TwinCAT provides a logging facility in the standard library: [AdsLogStr](https://infosys.beckhoff.com/index.php?content=../content/1031/tcplclibsystem/html/TcPlcLibSys_ADSLOGSTR.htm&id=). This function, which is available for the data types `LREAL`, `DINT` and `STRING`, allows ADS messages to be output as a text box on the screen and to the ADS console. A mask that is passed to the block can be used to configure which log level and which destination (console or text box) the message has. - -And that's pretty much all you can do in TwinCAT when it comes to logging. - -Therefore I started the open source project **TcLog**. TcLog is a logging framework that can be integrated as a library in TwinCAT. It allows a flexible configuration of the logs as well as the specification of different log targets. - -The source code and the precompiled library is available at [GitHub](https://github.com/bengeisler/TcLog). - - -## TcLog: Flexible logging framework -In order not to reinvent the wheel, TcLog is based on existing logging solutions like [Serilog](https://github.com/serilog/serilog). Unlike Serilog, TcLog does *not* support [structured logging](https://messagetemplates.org). All log messages are converted directly to *strings*. - -TcLog provides a central static logger `TcLogCore` which can be configured via a *fluent interface*: +It's usage is as simple as this: +Configure the core logger in your project: ``` VAR - CoreLogger : TcLogCore(nBufferSize := 100000); + _coreLogger : TcLogCore(bufferSize := 100 * SIZEOF(BYTE) * MAX_STRINGLENGTH); END_VAR -CoreLogger +_coreLogger .WriteToAds() - .MinimumLevel(E_LogLevel.Warning) + .WriteToFile('c:\logs\', 'sensor_data.txt') + .MinimumLevel(LogLevels.Debug) .RunLogger(); ``` - -It is used via a second block `TcLog` with which the messages are then triggered. The parameter `nBufferSize` reflects the size of the string cache that is used to cache all logging calls in one cycle and write to the file afterwards. The buffer will expand dynamically if it is has been set too low. If there is no more memory available, a FIFO overflow error will be set. - -The logger can persist up to 100 messages per plc cycle. If you log more than that, the persistance mechanism will spread over several plc cycles. When logging less than 100 messages per cycle, it takes roughly `1.5 * # of consecutive cycles with logging` cycles to persist the messages; when logging more than 100 messages per cycle, multiply that value by a factor of 3. - -``` -VAR - Logger : TcLog; -END_VAR - -Logger.Debug('This is a debug message.'); -Logger.Error('This is an error message.'); -``` - -![Raising an error message](https://benediktgeisler.de/Error_message.png "Raising an error message") - -The first message was triggered with log level *Debug*, but the minimum threshold was set to *Warning*, therefore only the second message is displayed. `TcLog` provides the following message levels: -- `E_LogLevel.Debug` -- `E_LogLevel.Information` -- `E_LogLevel.Warning` -- `E_LogLevel.Error` -- `E_LogLevel.Fatal` - -### Static binding of `TcLog` to `TcLogCore` -All instances of `TcLog` occurring in the program are statically bound to the one instance of `TcLogCore` which provides the configuration of the logger. This instance must be called cyclically. - -This behavior is known as the [Singleton](https://cidesi.repositorioinstitucional.mx/jspui/bitstream/1024/170/1/M-ARNC-2017.pdf) design pattern. Meanwhile it is seen rather critically, since it limits the testability of software. For the Singleton speaks however that it possesses a small overhead. Once the configuration of the logger in `MAIN` is set up, logging can be triggered anywhere in the PLC program by `TcLog`. Due to the Singleton the configuration of the central static logger will automatically be used. Since simplicity is the primary goal of this library, the advantages of the Singleton prevail. - -### Variable design of the message text - -TcLog implements a [StringBuilder](https://www.plccoder.com/fluent-code/) which makes it easy to build your own message text: - +Then, maybe in a different POU, use `TcLog` to log messages: ``` VAR - myInt : INT := 10; - myVarInfo : __SYSTEM.VAR_INFO := __VARINFO(myInt); + _logger: TcLog; + _myInt : INT := 10; + _myVarInfo : __SYSTEM.VAR_INFO := __VARINFO(_myInt); END_VAR -Logger +_logger .AppendString('Let´s log some values: ') - .AppendAny(myInt) + .AppendAny(_myInt) .AppendString(' - or some symbols: ') - .AppendVariable(myVarInfo, myInt) + .AppendVariable(_myVarInfo, _myInt) .Error(''); ``` +This will log both messages to both the ADS output and the file system. -![Using a StringBuilder to generate the message text](https://benediktgeisler.de/StringBuilder_in_message_text.png "Using a StringBuilder to generate the message text") - -Thus any amount of information can be appended to the message without having to implement `TcLog` with a large number of input parameters, since TwinCAT does not allow optional input parameters. - -The use of a *fluent interface* brings another advantage: future changes to the block provide new functionality via new methods, not via new input parameters. This means that existing code does not have to be adapted. - -### Including the instance path in the log message -TcLog offers with `.IncludeInstancePath()` the possibility to include the location where the message was triggered into the message text: - -``` -CoreLogger - .WriteToAds() - .IncludeInstancePath() - .MinimumLevel(E_LogLevel.Warning) - .RunLogger(); - -Logger.Error('This is an error message.'); -``` - -![Including the instance path](https://benediktgeisler.de/InstancePath.png "Including the instance path") - -### Conditional logging -The most common use of logging will be in the form `IF ... THEN log() END_IF`. Therefore this query is already integrated in TcLog: - -``` -VAR - rTrigLog : R_TRIG; - bLog : BOOL; -END_VAR - -rTrigLog(CLK := bLog); -Logger - .OnCondition(rTrigLog.Q) - .Error('Only logs when OnCondition evaluates to TRUE.'); -``` - -### Logging on rising/falling edges -Since a log message is usually to be sent once in the event of a *status change*, TcLog also provides a block for this purpose: `TcLogTRIG`. In contrast to `TcLog`, a separate instance must be created for each use of this block, since the edge state is stored internally. The conditional execution can thus be further simplified: - -``` -VAR - rTrigLogger : TcLogTRIG; -END_VAR - -rTrigLogger - .OnRisingEdge(bLog) - .Error('rTrig Test'); -``` - -Likewise, logging can be triggered on falling edges with `OnFallingEdge(cond)`. - -## Persist logs to the file system -The features shown so far are a flexible wrapper for `AdsLogStr`, but alone do not justify a new framework. TcLog therefore brings the option to store logs in the file system in the form of text files. This option can be applied to `TcLogCore` via the method `.WriteToFile(path, filename)`: - -``` -fbCoreLogger - .IncludeInstancePath() - .MinimumLevel(E_LogLevel.Warning) - .WriteToFile('c:\logs\', 'test.txt') - .RunLogger(); - -rTrigLogger - .OnRisingEdge(bLog) - .Error('rTrig Test'); -``` - -![Logging to the file system](https://benediktgeisler.de/LogMessageInFiileSystem.png "Logging to the file system") - -The file name is additionally prefixed with the creation date of the log file. The format of the date can be defined arbitrarily by means of a format string. Example: - -*YYMMDD-hh:mm:ss:iii* - -**Important**: Upper and lower case must be maintained, furthermore the same letters must always be placed one after the other. Blocks of identical letters are not permitted: ~~*YYMMDD-YYYY*~~ - -This format is passed to `TcLogCore` via the method `.TimestampFormat('YYMMDD-hh:mm:ss:iii')`. - -Since [TwinCAT can only write to the local file system](https://alltwincat.com/2019/11/11/logging-of-files-to-a-network-drive/), this restriction also applies to TcLog. - -### Custom delimiters -`TcLogCore` can be configured to use an arbitrary delimiter between the components of the log entry with `.SetDelimiter('|')`. - -### Set the rolling interval -A *rolling interval* denotes the interval until a new log file is created. TcLog offers the possibility to create a new logfile in regular intervals. This *rolling interval* is specified to `TcLogCore` via `SetRollingInterval(..)`: -- `E_RollingInterval.None`: Do not create a new log file. -- `E_RollingInterval.Hourly`: Create a new log file every hour -- `E_RollingInterval.Daily`: Create a new log file daily -- `E_RollingInterval.Monthly`: Create a new log file every month. - -The log file is only created when a message is triggered. - -### Delete old log files -To get rid of old log files, a lifespan of logs can be set with help of the method `DeleteLogsAfterDays(days)` of `TcLogCore`. Log files whose lifespan exceed the specified limit will automatically be deleted at midnight. - -## Customizing the logging -### Use of multiple loggers -Even though the logger was primarily designed as a singleton, it is possible to use multiple loggers. For example, sensor data can be collected cyclically and stored in a separate log file. To add another logger, an instance of 'TcLogCore' must be created. This is then bound to the desired `TcLog` instance: - -``` -VAR - newLogger: TcLogCore; - Logger : TcLog; - myInt : INT := 10; -END_VAR - -newLogger - .MinimumLevel(E_LogLevel.Information) - .SetRollingInterval(E_RollingInterval.Hourly) - .WriteToFile('c:\logs\', 'sensor_data.txt') - .DeleteLogFilesAfterDays(7) - .RunLogger(); - -Logger.SetLogger(newLogger); - -Logger - .AppendString('Sensor xy: ') - .AppendAny(myInt) - .Information(''); -``` - -From now on `Logger` considers the configuration of `newLogger`. - -### Custom logging templates -If one wants to record sensor data instead of the standard logs, for example, this is possible. The easiest way to do this is to program a wrapper around `TcLog` that enforces the specific template. - -#### Example: Logging of sensor data - -Suppose we want to record sensor data in `REAL` format. The data is to be saved in a csv file that has the following format: - -`hh:mm:ss;Betriebsmittelkennzeichen;Wert;Einheit` -`10:33:15;+CC1-B31;35.1;°C` - -#### Wrapper around `TcLog` - -As wrapper we use an FB that encapsulates `TcLog` and enforces the data input with the help of the inputs. Furthermore it implements the interface `ILog` which establishes the link between logger and base logger. - -``` -FUNCTION_BLOCK UserLog IMPLEMENTS ILog -VAR_INPUT - condition: BOOL; - identification: STRING; - value: REAL; - unit: STRING; -END_VAR -VAR - GetTimeData: GenerateTimeData; - timestamp: STRING; -END_VAR -VAR_STAT - Logger: TcLog; -END_VAR - -GetTimeData(); -timestamp := GetTimeData.ToString('hh:mm:ss'); - -Logger - .OnCondition(condition) - .AppendString(timestamp) - .AppendString(';') - .AppendString(identification) - .AppendString(';') - .AppendAny(value) - .AppendString(';') - .AppendString(unit) - .ToCustomFormat(''); -``` - -We can use the helper function `GenerateTimeData`, which returns the current date and time formatted via the `.ToString(Format)` method. With its help we generate the timestamp of the sensor data. - -The `.ToCustomFormat('')` method at the end of the chain causes the message to be logged unchanged. No additional information like further timestamps or instance path will be appended. - -#### The interface `ILog` - -The interface is implemented by passing the logger reference to the `TcLog` instance: - -``` -METHOD SetLogger : BOOL -VAR_INPUT - Ref2Core : REFERENCE TO TcLogCore; -END_VAR - -Logger.SetLogger(Ref2Core); -``` - -#### Calling the wrapper - -For example in `MAIN` `TcLogCore` is called cyclically. If there is more than one instance of it, we can tell our logger which instance we want via `.SetLogger(Instance)`. Otherwise the configuration of the logger singleton is used. - -``` -VAR - newLogger: TcLogCore; - rTrigLog : R_TRIG; - bLog : BOOL; - myLog : UserLog; - myValue: REAL := 1.0; - myValue2: REAL := 2.0; -END_VAR - -newLogger - .MinimumLevel(E_LogLevel.Information) - .SetRollingInterval(E_RollingInterval.Hourly) - .WriteToFile('c:\logs\', 'sensor.csv') - .DeleteLogFilesAfterDays(1) - .RunLogger(); - -myLog.SetLogger(newLogger); -rTrigLog(CLK := bLog); - -myLog( - condition := rTrigLog.Q, - identification := '+CC1-B31', - value := myValue, - unit := '°C'); - -myLog( - condition := rTrigLog.Q, - identification := '+CC1-B32', - value := myValue2, - unit := '°C'); -``` - -As soon as logging is triggered via `bLog`, the csv file and the entries in it are created: +**Features** +- Log to ADS output +- Log to file system +- Fluent interface for easy configuration and usage +- Specification of minimum log level +- Set rolling interval for log files +- Delete old log files automatically +- Dynamically expanding log buffer +- Log messages with or without timestamp +- Custom log message formatting -![Custom logging](https://benediktgeisler.de/CustomLogging.png "Custom logging") +The project contains both unit ([TcUnit](https://tcunit.org)) and integration tests ([xUnit](https://xunit.net)). -### Use of custom loggers -`TcLogCore` implements the `ILogCore` interface which defines the `LogCustomFormat` and `LogStandardFormat` methods. -A custom logger with for example other log sinks can be created in two ways: -1. create a new FB that inherits from `TcLogCore`. Thereby the new FB can be extended by additional functions and at the same time brings along all methods that `TcLogCore` has. -2. create a new FB that implements the `ILogCore` interface. This way the logger can be rewritten from scratch. The interface ensures that the existing instances of `TcLog` in the code can still be used. +## Install TcLog +See the [installation guide](docs/userguide/installation.html) for instructions on how to install TcLog. -## Error messages -`TcCoreLog` provides information about internal error messages via the `Error` property. +## Getting started +Get quickly up and running with TcLog: [Get Started](docs/userguide/getting_started.html) -``` -VAR - error: ST_Error; -END_VAR +## API reference +Find the full API reference [here](docs/reference/TcLog/Constants.html). -error := newLogger.Error; -``` - -![ST_Error](https://benediktgeisler.de/ST_Error.png "ST_Error") +## License +The library is licensed under the [MIT License](LICENSE). -## Unit- and integration tests -The project on Github contains both unit ([TcUnit](https://tcunit.org)) and integration tests ([xUnit](https://xunit.net)). +## Contributing +Contributions are welcome. Please see the [contribution guide](CONTRIBUTING.md) for details. ## Further ways of logging in TwinCAT With [log4TC](https://mbc-engineering.github.io/log4TC/index.html) there is another logging option for TwinCAT. This enables structured logging, but an additional Windows service must be installed, which communicates with the PLC library. `TcLog` on the other hand comes as a pure PLC library. The code for log4TC has been published as open source on [GitHub](https://github.com/mbc-engineering/log4TC/releases). + +## Disclaimer +This project is not affiliated with Beckhoff Automation GmbH & Co. KG and was first published in 2021 at [my blog](https://benediktgeisler.de/en/blog/tclog/). \ No newline at end of file diff --git a/TcLog.library b/TcLog.library deleted file mode 100644 index 6cc4710..0000000 Binary files a/TcLog.library and /dev/null differ diff --git a/TcLogProj/TcLog/Logger/DataTypes/Error.TcDUT b/TcLogProj/TcLog/Logger/DataTypes/Error.TcDUT deleted file mode 100644 index 79810e1..0000000 --- a/TcLogProj/TcLog/Logger/DataTypes/Error.TcDUT +++ /dev/null @@ -1,13 +0,0 @@ - - - - E_ErrorCode - Info : Tc2_System.T_MaxString; // Error message -END_STRUCT -END_TYPE -]]> - - \ No newline at end of file diff --git a/TcLogProj/TcLog/Logger/DataTypes/LoggingConfiguration.TcDUT b/TcLogProj/TcLog/Logger/DataTypes/LoggingConfiguration.TcDUT deleted file mode 100644 index f3b09b5..0000000 --- a/TcLogProj/TcLog/Logger/DataTypes/LoggingConfiguration.TcDUT +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TcLogProj/TcLogTest/NET/TestWrapper_NET.TcPOU.bak b/TcLogProj/TcLogTest/NET/TestWrapper_NET.TcPOU.bak deleted file mode 100644 index ea0ebbb..0000000 --- a/TcLogProj/TcLogTest/NET/TestWrapper_NET.TcPOU.bak +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - ActualTime); -fbSetSystemTime( - NETID := '', - TIMESTR := STRING_TO_SYSTEMTIME(System_time), - START := Set_system_time, - TMOUT := T#2S - ); - -// Execute tests -IF Persist_simple_error_message_run THEN - Persist_simple_error_message_run := FALSE; - Logger - .Error(Persist_simple_error_message_data); -END_IF - -IF Persist_long_error_message_run THEN - Persist_long_error_message_run := FALSE; - Logger - .Error(Persist_long_error_message_data); -END_IF - -IF Do_not_persist_logs_below_log_level_run THEN - Do_not_persist_logs_below_log_level_run := FALSE; - Logger - .Information('Nothing'); -END_IF - -IF Log_message_contains_instance_path_run THEN - Log_message_contains_instance_path_run := FALSE; - Logger - .Error('Nothing'); -END_IF - -IF Log_message_uses_correct_delimiter_run THEN - Log_message_uses_correct_delimiter_run := FALSE; - Logger - .Error(Log_message_uses_correct_delimiter_data); -END_IF - -IF Log_message_contains_custom_formatted_timestamp_run THEN - Log_message_contains_custom_formatted_timestamp_run := FALSE; - Logger - .Error('Test message'); -END_IF - -IF Delete_logs_if_expired_run THEN - Delete_logs_if_expired_run := FALSE; - Logger - .Error('Test message'); -END_IF - -IF New_logfile_is_created_if_rolling_interval_rolls_run THEN - New_logfile_is_created_if_rolling_interval_rolls_run := FALSE; - Logger - .Error('Test message'); -END_IF - -IF Same_log_file_is_used_until_rolling_interval_rolls THEN - Same_log_file_is_used_until_rolling_interval_rolls := FALSE; - Logger - .Error('Test message'); -END_IF - -IF Log_in_consecutive_cycles THEN - Logger - .Error(CONCAT('Logging cycle ', INT_TO_STRING(Cycles))); - Cycles := Cycles + 1; - IF Cycles >= Number_of_log_cycles THEN - Cycles := 0; - Log_in_consecutive_cycles := FALSE; - END_IF -END_IF - -IF Log_multiple_logs_in_one_cycle THEN - FOR i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO - Logger. - Error(CONCAT('Logging multiple times per cycle. Current step: ', INT_TO_STRING(i))); - END_FOR - Log_multiple_logs_in_one_cycle := FALSE; -END_IF - -IF Log_multiple_logs_in_multiple_cycles THEN - IF CycleCount <= Number_of_cycles THEN - FOR i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO - Logger - .AppendString('Logging multiple times per cycle. Cycle: ') - .AppendString(DINT_TO_STRING(CycleCount)) - .AppendString(' / Step: ') - .AppendString(INT_TO_STRING(i)) - .Error(''); - END_FOR - CycleCount := CycleCount + 1; - ELSE - CycleCount := 1; - Log_multiple_logs_in_multiple_cycles := FALSE; - END_IF -END_IF - -Persistance_time_stays_within_bounds_F_TRIG(CLK := (Persistance_time_stays_within_bounds AND CoreLogger.Busy)); -IF Persistance_time_stays_within_bounds_F_TRIG.Q THEN - CycleCount := 1; - Persistance_time_stays_within_bounds := FALSE; -END_IF -IF Persistance_time_stays_within_bounds THEN - Duration_in_cylces := Duration_in_cylces + 1; - IF CycleCount <= Number_of_cycles THEN - FOR i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO - Logger - .AppendString('Logging multiple times per cycle. Cycle: ') - .AppendString(DINT_TO_STRING(CycleCount)) - .AppendString(' / Step: ') - .AppendString(INT_TO_STRING(i)) - .Error(''); - END_FOR - CycleCount := CycleCount + 1; - END_IF -END_IF - - -]]> - - - \ No newline at end of file diff --git a/TcLogProj/TcLogTest/PlcTask.TcTTO b/TcLogProj/TcLogTest/PlcTask.TcTTO deleted file mode 100644 index fb868a3..0000000 --- a/TcLogProj/TcLogTest/PlcTask.TcTTO +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 10000 - 20 - - MAIN - - {e778e822-4c7c-4cb6-b932-d8bb93d51bac} - {46378c7c-9709-46bc-89d5-bb0957baa365} - {abac558f-e7b2-4db8-b44a-779cf7a752ba} - {d6193c87-3415-4305-a1ec-b2c1374e4242} - {d65a3d83-3e06-4b41-aa06-73da9d73501a} - - \ No newline at end of file diff --git a/TcLogProj/TcLogTest/TcLogTest.plcProj.bak b/TcLogProj/TcLogTest/TcLogTest.plcProj.bak deleted file mode 100644 index 05cceb2..0000000 --- a/TcLogProj/TcLogTest/TcLogTest.plcProj.bak +++ /dev/null @@ -1,150 +0,0 @@ - - - 1.0.0.0 - 2.0 - {67a5cd22-d1e6-4601-ac63-4cebe4a64230} - True - true - false - false - TcLogTest - 3.1.4023.0 - {52251088-d347-4476-bbc7-23fde6e8d23d} - {1c7f7d07-287f-4a51-b3cf-59c3170ec015} - {5ea0a77b-9d3a-43a7-baf8-d3dd93164ec5} - {e1bd9ded-0a02-4a11-be2c-80377a5672da} - {13ddf777-9176-46a7-9f0b-b060f7161474} - {bd225dc8-4884-4294-9c3c-d3a849af676d} - false - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - - - - Tc2_Standard, * (Beckhoff Automation GmbH) - Tc2_Standard - - - Tc2_System, * (Beckhoff Automation GmbH) - Tc2_System - - - Tc2_Utilities, * (Beckhoff Automation GmbH) - Tc2_Utilities - - - Tc3_Module, * (Beckhoff Automation GmbH) - Tc3_Module - - - TcLog, * (Benedikt Geisler) - TcLog - - - TcUnit, * (www.tcunit.org) - TcUnit - - - - - Content - - - - - TcLog, * (Benedikt Geisler) - - - - - - - - "<ProjectRoot>" - - {8F99A816-E488-41E4-9FA3-846536012284} - - "{8F99A816-E488-41E4-9FA3-846536012284}" - - - - {29BD8D0C-3586-4548-BB48-497B9A01693F} - - "{29BD8D0C-3586-4548-BB48-497B9A01693F}" - - Rules - - "Rules" - - - - - - - {40450F57-0AA3-4216-96F3-5444ECB29763} - - "{40450F57-0AA3-4216-96F3-5444ECB29763}" - - - ActiveVisuProfile - IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= - - - {192FAD59-8248-4824-A8DE-9177C94C195A} - - "{192FAD59-8248-4824-A8DE-9177C94C195A}" - - - - {F66C7017-BDD8-4114-926C-81D6D687E35F} - - "{F66C7017-BDD8-4114-926C-81D6D687E35F}" - - - - {246001F4-279D-43AC-B241-948EB31120E1} - - "{246001F4-279D-43AC-B241-948EB31120E1}" - - - GlobalVisuImageFilePath - %APPLICATIONPATH% - - - - - - - - System.Collections.Hashtable - {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} - System.String - - - - - \ No newline at end of file diff --git a/TcLogProj/TcLogTest/TcLogTest.plcproj b/TcLogProj/TcLogTest/TcLogTest.plcproj deleted file mode 100644 index 62a8423..0000000 --- a/TcLogProj/TcLogTest/TcLogTest.plcproj +++ /dev/null @@ -1,149 +0,0 @@ - - - - 1.0.0.0 - 2.0 - {4d70d393-c90d-46c3-86ae-bdc75c79053f} - True - true - false - false - TcLogTest - 3.1.4023.0 - {0cbede17-3f7f-4622-9cfd-62e3bf399640} - {b0c972a6-08d3-46e9-98b7-a4ef4e8d9b31} - {9b71c67e-f119-4640-ba0b-98e46c60e097} - {f7393b00-9f97-44d7-89c9-7038336b8b0d} - {a4da123a-4ea3-4422-b5d4-7ae1d2501723} - {d9ddb8a1-a1e0-4318-9382-2fb0cbf99920} - false - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - - Tc2_Standard, * (Beckhoff Automation GmbH) - Tc2_Standard - - - Tc2_System, * (Beckhoff Automation GmbH) - Tc2_System - - - Tc2_Utilities, * (Beckhoff Automation GmbH) - Tc2_Utilities - - - Tc3_Module, * (Beckhoff Automation GmbH) - Tc3_Module - - - TcLog, * (Benedikt Geisler) - TcLog - - - TcUnit, * (www.tcunit.org) - TcUnit - - - - - Content - - - - - TcLog, * (Benedikt Geisler) - - - - - - - - "<ProjectRoot>" - - {8F99A816-E488-41E4-9FA3-846536012284} - - "{8F99A816-E488-41E4-9FA3-846536012284}" - - - - {29BD8D0C-3586-4548-BB48-497B9A01693F} - - "{29BD8D0C-3586-4548-BB48-497B9A01693F}" - - Rules - - "Rules" - - - - - - - {40450F57-0AA3-4216-96F3-5444ECB29763} - - "{40450F57-0AA3-4216-96F3-5444ECB29763}" - - - ActiveVisuProfile - IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= - - - {192FAD59-8248-4824-A8DE-9177C94C195A} - - "{192FAD59-8248-4824-A8DE-9177C94C195A}" - - - - {F66C7017-BDD8-4114-926C-81D6D687E35F} - - "{F66C7017-BDD8-4114-926C-81D6D687E35F}" - - - - {246001F4-279D-43AC-B241-948EB31120E1} - - "{246001F4-279D-43AC-B241-948EB31120E1}" - - - GlobalVisuImageFilePath - %APPLICATIONPATH% - - - - - - - - System.Collections.Hashtable - {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} - System.String - - - - - \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..a4294d4 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,33 @@ +{ + "build": { + "content": [ + { + "files": [ + "userguide/*.md", + "userguide/toc.yml", + "reference/**/*.md", + "reference/toc.yml", + "reference/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": ["images/**"] + } + ], + "dest": "html", + "fileMetadataFiles": [], + "template": [ + "statictoc", "template" + ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} diff --git a/TcLog_header.svg b/docs/images/TcLog_header.svg similarity index 100% rename from TcLog_header.svg rename to docs/images/TcLog_header.svg diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..4f399fa --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +# TcLog +`TcLog` is a flexible logging framework for TwinCAT 3 that allows to log to both the ADS output and the file system. It is designed to be easy to use and to integrate into your project. It has no external dependencies and is available as library for TwinCAT 3. + +It features the following: +- Log to ADS output +- Log to file system +- Fluent interface for easy configuration and usage +- Specification of minimum log level +- Set rolling interval for log files +- Delete old log files automatically +- Dynamically expanding log buffer +- Log messages with or without timestamp +- Custom log message formatting + +The library is fully unit- and integration-tested. + +## Install TcLog +See here how to install TcLog: + + +## Getting started +Get quickly up and running with TcLog: + + +## Documentation +Find the full API reference here: + + +## License +The library is licensed under the [MIT License](../LICENSE). \ No newline at end of file diff --git a/docs/metadata.json b/docs/metadata.json new file mode 100644 index 0000000..871d9a1 --- /dev/null +++ b/docs/metadata.json @@ -0,0 +1,5 @@ +{ + "_appTitle": "TcLog", + "_appFooter": "(c) 2023 Ben Geisler - Documentation powered by Zeugwerk GmbH", + "_disableContribution": false +} diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..7063601 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,4 @@ +- name: User guide + href: userguide/ +- name: API Reference + href: reference/ diff --git a/docs/userguide/configuration.md b/docs/userguide/configuration.md new file mode 100644 index 0000000..ef985ed --- /dev/null +++ b/docs/userguide/configuration.md @@ -0,0 +1,111 @@ +# Configuration +`TcLogCore` is used to build the logging configuration and to run the persistence mechanism. + +## Message format +The message format can be adapted with several methods of `TcLogCore` that are described in the following. + +### Delimiter +`TcLogCore` can be configured to use an arbitrary delimiter between the components of the log entry with `.SetDelimiter('|')`. + +### Including the instance path in the log message +TcLog offers with `.IncludeInstancePath()` the possibility to include the location where the message was triggered into the message text: + +``` +_coreLogger + .WriteToAds() + .IncludeInstancePath() + .MinimumLevel(LogLevels.Warning) + .RunLogger(); + +_logger.Error('This is an error message.'); +``` + +![Including the instance path](https://benediktgeisler.de/InstancePath.png "Including the instance path") + +## Log to ADS output +When adding the method `.WriteToAds()` to `TcLogCore`, the log messages are sent to the ADS output: + +``` +_coreLogger + .WriteToAds() + .RunLogger(); +``` + +## Log to file system +TcLog brings the option to store logs in the file system in the form of text files. This option can be applied to `TcLogCore` via the method `.WriteToFile(path, filename)`: + +``` +_coreLogger + .IncludeInstancePath() + .MinimumLevel(LogLevels.Warning) + .WriteToFile('c:\logs\', 'test.txt') + .RunLogger(); + +_loggerTrig + .OnRisingEdge(_log) + .Error('rTrig Test'); +``` + +![Logging to the file system](https://benediktgeisler.de/LogMessageInFiileSystem.png "Logging to the file system") + +### Timestamp +The file name is additionally prefixed with the creation date of the log file. The format of the date can be defined arbitrarily by means of a format string. Example: + +*YYMMDD-hh:mm:ss:iii* + +> [!IMPORTANT] +> Upper and lower case must be maintained, furthermore the same letters must always be placed one after the other. Blocks of identical letters are not permitted: ~~*YYMMDD-YYYY*~~ + +This format is passed to `TcLogCore` via the method `.TimestampFormat('YYMMDD-hh:mm:ss:iii')`. + +## Minimum log level +With the method `.MinimumLevel(level)` of `TcLogCore` the minimum log level can be specified. All messages with a lower log level are ignored. + +TcLog supports the following log levels: +- `LogLevels.Debug` +- `LogLevels.Information` +- `LogLevels.Warning` +- `LogLevels.Error` +- `LogLevels.Fatal` + + +## Rolling interval +A *rolling interval* denotes the interval until a new log file is created. TcLog offers the possibility to create a new logfile in regular intervals. This *rolling interval* is specified to `TcLogCore` via `SetRollingInterval(..)`: +- `RollingIntervals.None`: Do not create a new log file. +- `RollingIntervals.Hourly`: Create a new log file every hour +- `RollingIntervals.Daily`: Create a new log file daily +- `RollingIntervals.Monthly`: Create a new log file every month. + +The log file is only created when a message is triggered. + +## Delete old log files +To get rid of old log files, a lifespan of logs can be set with help of the method `DeleteLogsAfterDays(days)` of `TcLogCore`. Log files whose lifespan exceed the specified limit will automatically be deleted at midnight. + +## Starting the logger +After the configuration is complete, the logger is started with the method `RunLogger()` of `TcLogCore`. + +## Using different verbosity levels +Different scenarios may require different logging strategies. For example, you may want to log all messages with a log level of `Error` or higher in production, but all messages with a log level of `Debug` or higher in development. You can achieve this like this: + +``` +VAR + _coreLogger : TcLogCore(bufferSize := 100 * SIZEOF(BYTE) * MAX_STRINGLENGTH); + _logger : TcLog; + _isDevelopment : BOOL := TRUE; +END_VAR + +_coreLogger + .WriteToAds() + .WriteToFile('c:\logs\', 'sensor_data.txt'); + +IF _isDevelopment THEN + _coreLogger.MinimumLevel(LogLevels.Debug); +ELSE + _coreLogger.MinimumLevel(LogLevels.Error); +END_IF + +_coreLogger.RunLogger(); + +_logger.Debug('This is a debug message.'); +_logger.Error('This is an error message.'); +``` diff --git a/docs/userguide/customization.md b/docs/userguide/customization.md new file mode 100644 index 0000000..553dcda --- /dev/null +++ b/docs/userguide/customization.md @@ -0,0 +1,114 @@ +# Customization +TcLog is design to be easily customizable. This page shows how to customize the logger to your needs. + +## Custom logging templates +If one wants to record sensor data instead of the standard logs, for example, this is possible. The easiest way to do this is to program a wrapper around `TcLog` that enforces the specific template. + +### Example: Logging of sensor data + +Suppose we want to record sensor data in `REAL` format. The data is to be saved in a csv file that has the following format: + +`hh:mm:ss;device designation;value;unit` + +And the output should look like this: + +`10:33:15;+CC1-B31;35.1;°C` + +### Wrapper around `TcLog` + +As wrapper we use an function block that encapsulates `TcLog` and enforces the data input with the help of the inputs. Furthermore it implements the interface `ILog` which establishes the link between logger and base logger. + +``` +FUNCTION_BLOCK UserLog IMPLEMENTS ILog +VAR_INPUT + Condition: BOOL; + Identification: STRING; + Value: REAL; + Unit: STRING; +END_VAR +VAR + _getTimeData: DateTime; + _timestamp: STRING; +END_VAR +VAR_STAT + _logger: TcLog; +END_VAR + +_getTimeData(); +_timestamp := _getTimeData.ToString('hh:mm:ss'); + +_logger + .OnCondition(Condition) + .AppendString(_timestamp) + .AppendString(';') + .AppendString(Identification) + .AppendString(';') + .AppendAny(Value) + .AppendString(';') + .AppendString(Unit) + .ToCustomFormat(''); +``` + +We can use the helper function `GenerateTimeData`, which returns the current date and time formatted via the `.ToString(Format)` method. With its help we generate the timestamp of the sensor data. + +The `.ToCustomFormat('')` method at the end of the chain causes the message to be logged unchanged. No additional information like further timestamps or instance path will be appended. + +### The interface `ILog` + +The interface is implemented by passing the logger reference to the `TcLog` instance: + +``` +METHOD SetLogger : BOOL +VAR_INPUT + ref2Core : REFERENCE TO TcLogCore; +END_VAR + +_logger.SetLogger(ref2Core); +``` + +### Calling the wrapper + +Somewhere in our program `TcLogCore` is called cyclically. If there is more than one instance of it, we can tell our logger which instance we want via `.SetLogger(Instance)`. Otherwise the configuration of the logger singleton is used. + +``` +VAR + _newLogger: TcLogCore; + _rTrigLog : R_TRIG; + _log : BOOL; + _myLog : UserLog; + _myValue: REAL := 1.0; + _myValue2: REAL := 2.0; +END_VAR + +_newLogger + .MinimumLevel(LogLevels.Information) + .SetRollingInterval(RollingIntervals.Hourly) + .WriteToFile('c:\logs\', 'sensor.csv') + .DeleteLogFilesAfterDays(1) + .RunLogger(); + +_myLog.SetLogger(_newLogger); +_rTrigLog(CLK := _log); + +_myLog( + Condition := _rTrigLog.Q, + Identification := '+CC1-B31', + Value := _myValue, + Unit := '°C'); + +_myLog( + Condition := _rTrigLog.Q, + Identification := '+CC1-B32', + Value := _myValue2, + Unit := '°C'); +``` + +As soon as logging is triggered via `_log`, the csv file and the entries in it are created: + +![Custom logging](https://benediktgeisler.de/CustomLogging.png "Custom logging") + +## Use of custom loggers +`TcLogCore` implements the `ILogCore` interface which defines the `LogCustomFormat` and `LogStandardFormat` methods. +A custom logger with for example other log sinks can be created in two ways: +1. create a new FB that inherits from `TcLogCore`. Thereby the new FB can be extended by additional functions and at the same time brings along all methods that `TcLogCore` has. +2. create a new FB that implements the `ILogCore` interface. This way the logger can be rewritten from scratch. The interface ensures that the existing instances of `TcLog` in the code can still be used. \ No newline at end of file diff --git a/docs/userguide/getting_started.md b/docs/userguide/getting_started.md new file mode 100644 index 0000000..2c20bcf --- /dev/null +++ b/docs/userguide/getting_started.md @@ -0,0 +1,32 @@ +# Getting started +`TcLog` has two main building blocks: +- `TcLog`, which is used to log messages. +- `TcLogCore`, which is the central static logger that takes care of processing the logged messages, such as sending them to ADS ouput or persisting them to the file system. + +You would typically call `TcLogCore` once in your project and configure the logger behaviour. Then, you would use `TcLog` to log messages. Each call of `TcLog` then uses the same configuration specified with `TcLogCore`. While there is normally only one instance of `TcLogCore` in your project, you can create as many instances of `TcLog` as you like, typically one in each POU. Both `TcLog` and `TcLogCore` provide fluent interfaces to make configuration and log message creation as easy as possible. + +## Example usage +Configure the core logger in your project: +``` +VAR + _coreLogger : TcLogCore(bufferSize := 100 * SIZEOF(BYTE) * MAX_STRINGLENGTH); +END_VAR + +_coreLogger + .WriteToAds() + .WriteToFile('c:\logs\', 'sensor_data.txt') + .MinimumLevel(LogLevels.Debug) + .RunLogger(); +``` +Then, maybe in a different POU, use `TcLog` to log messages: +``` +VAR + _logger : TcLog; +END_VAR + +_logger.Debug('This is a debug message.'); +_logger.Error('This is an error message.'); +``` +This will log both messages to both the ADS output and the file system. + +Next, see how to [configure TcLog](configuration.md) and how to [use TcLog in detail](logging.md). \ No newline at end of file diff --git a/docs/userguide/installation.md b/docs/userguide/installation.md new file mode 100644 index 0000000..db6fe5b --- /dev/null +++ b/docs/userguide/installation.md @@ -0,0 +1,15 @@ +# Installation +There are two ways to install this library: +- download the latest release and install it manually or +- install it via Twinpack. + +## Manual Installation + +Download the latest release from [here](https://github.com/bengeisler/TcLog/releases/latest) and install it manually in your project. See the [Beckhoff documentation](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/4218300427.html&id=) on how to do this. + +## Twinpack +First, [install Twinpack](https://github.com/Zeugwerk/Twinpack#installation). + +Then, search for `TcLog` in the [Twinpack package manager](https://github.com/Zeugwerk/Twinpack#using-a-package) and install it. + + diff --git a/docs/userguide/license.md b/docs/userguide/license.md new file mode 100644 index 0000000..0e4324b --- /dev/null +++ b/docs/userguide/license.md @@ -0,0 +1,22 @@ +# License +MIT License + +Copyright (c) 2021 bengeisler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/docs/userguide/logging.md b/docs/userguide/logging.md new file mode 100644 index 0000000..06a07a7 --- /dev/null +++ b/docs/userguide/logging.md @@ -0,0 +1,84 @@ +# Logging +## Flexible logging +TcLog implements a [StringBuilder](https://www.plccoder.com/fluent-code/) which makes it easy to build your own message text: + +``` +VAR + _logger: TcLog; + _myInt : INT := 10; + _myVarInfo : __SYSTEM.VAR_INFO := __VARINFO(_myInt); +END_VAR + +_logger + .AppendString('Let´s log some values: ') + .AppendAny(_myInt) + .AppendString(' - or some symbols: ') + .AppendVariable(_myVarInfo, _myInt) + .Error(''); +``` +![Using a StringBuilder to generate the message text](https://benediktgeisler.de/StringBuilder_in_message_text.png "Using a StringBuilder to generate the message text") + +Thus any amount of information can be appended to the message without having to implement `TcLog` with a large number of input parameters, since TwinCAT (at least in version before build 4026.0) does not allow optional input parameters. + +The methods `AppendString`, `AppendAny` and `AppendVariable` append the passed in data to the message text. + +The methods `Debug`, `Info`, `Warning`, `Error` and `Fatal` log the message with the respective log level. If you only want to log a simple string, you can pass it directly to the respective method, e.g. `_logger.Debug('This is a debug message.')`. + +## Conditional logging +The most common use of logging will be in the form `IF ... THEN log() END_IF`. Therefore this query is already integrated in TcLog: + +``` +VAR + _logger: TcLog; + _triggerLogging : R_TRIG; + _log : BOOL; +END_VAR + +_triggerLogging(CLK := _log); +_logger + .OnCondition(_triggerLogging.Q) + .Error('Only logs when OnCondition evaluates to TRUE.'); +``` + +## Logging on rising/falling edges +Since a log message is usually to be sent once in the event of a *status change*, TcLog also provides a block for this purpose: `TcLogTrig`. In contrast to `TcLog`, a separate instance must be created for each use of this block, since the edge state is stored internally. The conditional execution can thus be further simplified: + +``` +VAR + _loggerTrig : TcLogTRIG; + _log : BOOL; +END_VAR + +_loggerTrig + .OnRisingEdge(_log) + .Error('rTrig Test'); +``` + +Likewise, logging can be triggered on falling edges with `OnFallingEdge(cond)`. + +## Use of multiple loggers +Even though the logger was primarily designed as a singleton, it is possible to use multiple loggers. For example, sensor data can be collected cyclically and stored in a separate log file. To add another logger, an instance of `TcLogCore` must be created. This is then bound to the desired `TcLog` instance: + +``` +VAR + _newLogger: TcLogCore; + _logger: TcLog; + _myInt : INT := 10; +END_VAR + +_newLogger + .MinimumLevel(LogLevels.Information) + .SetRollingInterval(RollingIntervals.Hourly) + .WriteToFile('c:\logs\', 'sensor_data.txt') + .DeleteLogFilesAfterDays(7) + .RunLogger(); + +// Bind the new logger to the TcLog instance +_logger.SetLogger(_newLogger); + +_logger.AppendString('Sensor xy: ') + .AppendAny(_myInt) + .Information(''); +``` + +From now on `_logger` considers the configuration of `_newLogger`. \ No newline at end of file diff --git a/docs/userguide/performance.md b/docs/userguide/performance.md new file mode 100644 index 0000000..32cd413 --- /dev/null +++ b/docs/userguide/performance.md @@ -0,0 +1,2 @@ +# Performance +The logger can persist up to 100 messages per plc cycle. If you log more than that, the persistance mechanism will spread over several plc cycles. When logging less than 100 messages per cycle, it takes roughly `1.5 * # of consecutive cycles with logging` cycles to persist the messages; when logging more than 100 messages per cycle, multiply that value by a factor of 3. \ No newline at end of file diff --git a/docs/userguide/toc.yml b/docs/userguide/toc.yml new file mode 100644 index 0000000..a163263 --- /dev/null +++ b/docs/userguide/toc.yml @@ -0,0 +1,16 @@ +- name: Installation + href: installation.md +- name: User Guide + href: getting_started.md +- name: User guide + items: + - name: Getting started + href: getting_started.md + - name: Logging + href: logging.md + - name: Configuration + href: configuration.md + - name: Customization + href: customization.md +- name: License + href: license.md \ No newline at end of file diff --git a/TcLog.sln b/src/TcLog.sln similarity index 100% rename from TcLog.sln rename to src/TcLog.sln diff --git a/TcLogProj/TcLog/CONSTANTS.TcGVL b/src/TcLogProj/TcLog/CONSTANTS.TcGVL similarity index 100% rename from TcLogProj/TcLog/CONSTANTS.TcGVL rename to src/TcLogProj/TcLog/CONSTANTS.TcGVL diff --git a/TcLogProj/TcLog/Logger/POUs/LogLevelToAdsLogMsgType.TcPOU b/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU similarity index 93% rename from TcLogProj/TcLog/Logger/POUs/LogLevelToAdsLogMsgType.TcPOU rename to src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU index 85d22b1..11acbce 100644 --- a/TcLogProj/TcLog/Logger/POUs/LogLevelToAdsLogMsgType.TcPOU +++ b/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU @@ -3,14 +3,14 @@ - - - - - - InvalidLoggerReference THEN - _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, Message), ShortenInstancePath(_instancePath), LogLevels.Debug); + _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Debug); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -118,17 +137,18 @@ _logDataInitialized := FALSE;]]> - InvalidLoggerReference THEN - _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, Message), ShortenInstancePath(_instancePath), LogLevels.Error); + _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Error); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -138,17 +158,18 @@ _logDataInitialized := FALSE; - InvalidLoggerReference THEN - _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, Message), ShortenInstancePath(_instancePath), LogLevels.Fatal); + _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Fatal); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -157,17 +178,18 @@ _logDataInitialized := FALSE;]]> - InvalidLoggerReference THEN - _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, Message), ShortenInstancePath(_instancePath), LogLevels.Information); + _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Information); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -176,20 +198,20 @@ _logDataInitialized := FALSE;]]> - - + @@ -202,38 +224,40 @@ END_IF]]> - - - + - 0) DO @@ -262,39 +286,41 @@ shortenedPath := Tc2_Standard.DELETE(shortenedPath, positionInString+1, 0); positionInString := Tc2_Standard.FIND(shortenedPath, '.'); shortenedPath := Tc2_Standard.DELETE(shortenedPath, positionInString+1, 0); -ShortenInstancePath := shortenedPath; -]]> +ShortenInstancePath := shortenedPath;]]> - - InvalidLoggerReference THEN - _usedLogger.LogCustomFormat(Tc2_Standard.CONCAT(_logData, Message)); + _usedLogger.LogCustomFormat(Tc2_Standard.CONCAT(_logData, message)); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -303,17 +329,18 @@ _logDataInitialized := FALSE;]]> - InvalidLoggerReference THEN - _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, Message), ShortenInstancePath(_instancePath), LogLevels.Warning); + _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Warning); ELSE Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); END_IF @@ -368,7 +395,6 @@ _logDataInitialized := FALSE;]]> - diff --git a/TcLogProj/TcLog/Logger/POUs/TcLogCore.TcPOU b/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU similarity index 64% rename from TcLogProj/TcLog/Logger/POUs/TcLogCore.TcPOU rename to src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU index 6dcfeda..7bb5e33 100644 --- a/TcLogProj/TcLog/Logger/POUs/TcLogCore.TcPOU +++ b/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU @@ -1,29 +1,34 @@  - [!NOTE] +/// > `TcLogCore.RunLogger()` should be called once each cycle. The configuration methods should be called **before** +/// > `RunLogger` is called. +/// +/// When declaring an instance of TcLogCore, the ininital buffer size has to be set. If the buffer size is set too low, it will expand +/// automatically if enough memory is available. +/// +/// To set the inital buffer size to accomodate 100 messages, use it like this: +/// ``` +/// VAR +/// myLogger: TcLogCore(BufferSize := 100 * SIZEOF(BYTE) * Tc2_System.MAX_STRING_LENGTH); +/// END_VAR +/// ``` +{attribute 'hide_all_locals'} +FUNCTION_BLOCK TcLogCore IMPLEMENTS ILogCore VAR - {attribute 'hide'} _loggingSingleton : TcLog; // Logger-Singleton: Required to provide Singleton-functionality for TcLog. - {attribute 'hide'} - _localTimeAsFileTime : Tc2_Utilities.T_FILETIME; // Actual time as filetime, corrected to local time zone - {attribute 'hide'} - _utcTimeAsFileTime : Tc2_Utilities.T_FILETIME; // Actual time as filetime - {attribute 'hide'} - _timeAsSystemTime : Tc2_Utilities.TIMESTRUCT; // Actual time as timestring - {attribute 'hide'} - _timeAsString : STRING; // Actual time as string - {attribute 'hide'} + _utcTimeAsFileTime : Tc2_Utilities.T_FILETIME; // Current utc time as filetime + _localTimeAsSystemTime : Tc2_Utilities.TIMESTRUCT; // Current local time as timestring + _localTimeAsString : STRING; // Current local time as string _logCache : DynamicStringBuffer; // Dynamic string buffer to cache logs to be processed - {attribute 'hide'} _startLoggingConfiguration : BOOL; // Aux variable to provide fluent interface. - {attribute 'hide'} - _internalErrorOccured : BOOL; // Aux variable to log internal errors to Error-property. - {attribute 'hide'} + _internalErrorOccured : BOOL; // Aux variable to log internal errors to Error-property. _config : LoggingConfiguration; // Logging configuration. Is set by invoking configuration methods. - {attribute 'hide'} _error : Error; // Contains information about internal error. - {attribute 'hide'} _busy : BOOL; // Logger is busy, typically with writing logs to the file system. END_VAR ]]> @@ -35,7 +40,6 @@ END_VAR @@ -55,7 +59,9 @@ END_IF]]> - @@ -73,7 +79,8 @@ PROPERTY Busy : BOOL - @@ -84,23 +91,23 @@ PROPERTY Configuration : LoggingConfiguration - - @@ -112,41 +119,40 @@ PROPERTY Error : REFERENCE TO Error - +_logCache.Init(DINT_TO_UDINT(bufferSize));]]> - +_logCache.Init(DINT_TO_UDINT(bufferSize));]]> - - @@ -158,51 +164,56 @@ IncludeInstancePath REF= This^;]]> - '' THEN - _logCache.AddLine(Data); + _logCache.AddLine(data); END_IF]]> - = _config.MinimumLevel THEN IF _config.WriteToAds THEN Tc2_System.ADSLOGSTR( - ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevel), - stringBuilder + ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(logLevel), + messageBuilder .Reset() - .AppendIf(_config.IncludeInstancePath, InstancePath) + .AppendIf(_config.IncludeInstancePath, instancePath) .AppendIf(_config.IncludeInstancePath, _config.Delimiter) - .Append(Data) + .Append(data) .ToString(), '' ); @@ -210,15 +221,15 @@ END_VAR IF _config.FileName <> '' THEN _logCache.AddLine( - stringBuilder + messageBuilder .Reset() - .Append(_timeAsString) + .Append(_localTimeAsString) .Append(_config.Delimiter) .Append(TO_STRING(LogLevel)) .AppendIf(_config.IncludeInstancePath, _config.Delimiter) - .AppendIf(_config.IncludeInstancePath, InstancePath) + .AppendIf(_config.IncludeInstancePath, instancePath) .Append(_config.Delimiter) - .Append(Data) + .Append(data) .ToString() ); END_IF @@ -227,18 +238,19 @@ END_IF - @@ -246,11 +258,11 @@ MinimumLevel REF=THIS^;]]> - hourRolled); -newDay(CLK := (hourRolled AND CurrentUtcTime.wHour = 0), +newDay(CLK := (hourRolled AND currentUtcTime.wHour = 0), Q => dayRolled); -newMonth(CLK := (dayRolled AND CurrentUtcTime.wDay = 1), +newMonth(CLK := (dayRolled AND currentUtcTime.wDay = 1), Q => monthRolled); -RollingIntervalReached := (hourRolled AND RollingInterval = RollingIntervals.Hourly) OR - (dayRolled AND RollingInterval = RollingIntervals.Daily) OR - (monthRolled AND RollingInterval = RollingIntervals.Monthly); +RollingIntervalReached := (hourRolled AND rollingInterval = RollingIntervals.Hourly) OR + (dayRolled AND rollingInterval = RollingIntervals.Daily) OR + (monthRolled AND rollingInterval = RollingIntervals.Monthly); ]]> - [!NOTE] +/// > This method should be the last method of `TcLogCore` to be called in each cycle. +{attribute 'hide_all_locals'} METHOD RunLogger : BOOL VAR_INST newDay : Tc2_Standard.R_TRIG; @@ -292,18 +307,18 @@ END_VAR 0), ExpirationInDays := _config.LogFileLifespan, @@ -347,57 +362,72 @@ _internalErrorOccured := FALSE;]]> - - - _20210812-12:30:22:123_ METHOD TimestampFormat : REFERENCE TO TcLogCore VAR_INPUT - Format : STRING; // Format the timestamp should have + /// Format the timestamp should have. + format : STRING; END_VAR ]]> - @@ -409,19 +439,25 @@ WriteToAds REF= This^;]]> - '\' THEN + _config.FilePath := TC2_Standard.CONCAT(path, '\'); +ELSE + _config.FilePath := path; +END_IF +_config.FileName := fileName; WriteToFile REF= This^;]]> @@ -482,7 +518,11 @@ WriteToFile REF= This^;]]> - + + + + + @@ -502,7 +542,13 @@ WriteToFile REF= This^;]]> - + + + + + + + diff --git a/TcLogProj/TcLog/Logger/POUs/TcLogTRIG.TcPOU b/src/TcLogProj/TcLog/Logger/TcLogTrig.TcPOU similarity index 50% rename from TcLogProj/TcLog/Logger/POUs/TcLogTRIG.TcPOU rename to src/TcLogProj/TcLog/Logger/TcLogTrig.TcPOU index 46e28f3..d0a996c 100644 --- a/TcLogProj/TcLog/Logger/POUs/TcLogTRIG.TcPOU +++ b/src/TcLogProj/TcLog/Logger/TcLogTrig.TcPOU @@ -1,16 +1,21 @@  - - + - - + + + + + + + + + + + \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Logger/_DataTypes/Error.TcDUT b/src/TcLogProj/TcLog/Logger/_DataTypes/Error.TcDUT new file mode 100644 index 0000000..390bedb --- /dev/null +++ b/src/TcLogProj/TcLog/Logger/_DataTypes/Error.TcDUT @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/TcLogProj/TcLog/Logger/DataTypes/ErrorCodes.TcDUT b/src/TcLogProj/TcLog/Logger/_DataTypes/ErrorCodes.TcDUT similarity index 80% rename from TcLogProj/TcLog/Logger/DataTypes/ErrorCodes.TcDUT rename to src/TcLogProj/TcLog/Logger/_DataTypes/ErrorCodes.TcDUT index 653ea86..642b8a8 100644 --- a/TcLogProj/TcLog/Logger/DataTypes/ErrorCodes.TcDUT +++ b/src/TcLogProj/TcLog/Logger/_DataTypes/ErrorCodes.TcDUT @@ -1,10 +1,10 @@  - - + + + + + \ No newline at end of file diff --git a/TcLogProj/TcLog/Logger/DataTypes/RollingIntervals.TcDUT b/src/TcLogProj/TcLog/Logger/_DataTypes/RollingIntervals.TcDUT similarity index 72% rename from TcLogProj/TcLog/Logger/DataTypes/RollingIntervals.TcDUT rename to src/TcLogProj/TcLog/Logger/_DataTypes/RollingIntervals.TcDUT index 6ce49bb..2327e79 100644 --- a/TcLogProj/TcLog/Logger/DataTypes/RollingIntervals.TcDUT +++ b/src/TcLogProj/TcLog/Logger/_DataTypes/RollingIntervals.TcDUT @@ -1,10 +1,10 @@  - - + + + + + 10000 + 20 + + TESTS + + {ddc00032-d9e1-4693-b40f-72bdfdc895f4} + {3a4ce825-9d50-49d3-8262-57abdbdb7d7d} + {81bc0bc2-89b6-4114-828c-a7910298d03e} + {3126c053-68c1-472f-a148-5aa48b2f5b2e} + {634737fd-9db2-44dc-a397-13a58eb2c2e8} + + \ No newline at end of file diff --git a/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU b/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU similarity index 90% rename from TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU rename to src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU index 6ac5439..eb9080b 100644 --- a/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU +++ b/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU @@ -13,7 +13,7 @@ END_VAR @@ -24,8 +24,8 @@ Append := THIS^;]]> @@ -38,21 +38,21 @@ AppendIf := THIS^;]]> - - - + - + diff --git a/TcLogProj/TcLog/TcLog.plcproj b/src/TcLogProj/TcLog/TcLog.plcproj similarity index 82% rename from TcLogProj/TcLog/TcLog.plcproj rename to src/TcLogProj/TcLog/TcLog.plcproj index b18ddee..19ac46e 100644 --- a/TcLogProj/TcLog/TcLog.plcproj +++ b/src/TcLogProj/TcLog/TcLog.plcproj @@ -25,14 +25,15 @@ TcLog provides logging functionality to TwinCAT. + + - + + - - - + + - @@ -40,67 +41,85 @@ Code true - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + Code - + + Code + + + Code + + + Code + + Code - + Code - + Code - + Code - + Code - + + Code + + + Code + + + Code + + Code @@ -133,6 +152,10 @@ Tc3_Module, * (Beckhoff Automation GmbH) Tc3_Module + + TcUnit, * (www.tcunit.org) + TcUnit + diff --git a/TcLogProj/TcLog/TcLog.plcproj.bak b/src/TcLogProj/TcLog/TcLog.plcproj.bak similarity index 100% rename from TcLogProj/TcLog/TcLog.plcproj.bak rename to src/TcLogProj/TcLog/TcLog.plcproj.bak diff --git a/TcLogProj/TcLog/Utils/POUs/AnyToString.TcPOU b/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU similarity index 62% rename from TcLogProj/TcLog/Utils/POUs/AnyToString.TcPOU rename to src/TcLogProj/TcLog/Utils/AnyToString.TcPOU index 38244dc..09ac59e 100644 --- a/TcLogProj/TcLog/Utils/POUs/AnyToString.TcPOU +++ b/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU @@ -4,7 +4,7 @@ - diff --git a/TcLogProj/TcLog/Utils/POUs/DateTime.TcPOU b/src/TcLogProj/TcLog/Utils/DateTime.TcPOU similarity index 73% rename from TcLogProj/TcLog/Utils/POUs/DateTime.TcPOU rename to src/TcLogProj/TcLog/Utils/DateTime.TcPOU index c1939c0..747a5cd 100644 --- a/TcLogProj/TcLog/Utils/POUs/DateTime.TcPOU +++ b/src/TcLogProj/TcLog/Utils/DateTime.TcPOU @@ -1,7 +1,9 @@  - - @@ -47,7 +51,9 @@ _localSystemTimeValid(CLK := _localSystemTime.bValid); - @@ -57,7 +63,9 @@ _localSystemTimeValid(CLK := _localSystemTime.bValid); - @@ -67,7 +75,9 @@ _localSystemTimeValid(CLK := _localSystemTime.bValid); - @@ -77,7 +87,9 @@ _localSystemTimeValid(CLK := _localSystemTime.bValid); - @@ -88,12 +100,13 @@ PROPERTY Done : BOOL - Tc2_Standard.LEN(timeString) THEN - FOR i:=1 TO Length - Tc2_Standard.LEN(timeString) DO +IF length > Tc2_Standard.LEN(timeString) THEN + FOR i:=1 TO length - Tc2_Standard.LEN(timeString) DO replaceString := Tc2_Standard.CONCAT(replaceString, '0'); END_FOR replaceString := Tc2_Standard.CONCAT(replaceString, timeString); ELSE - replaceString := Tc2_Standard.RIGHT(timeString, Length); + replaceString := Tc2_Standard.RIGHT(timeString, length); END_IF -ReplaceFormatPlaceholder := Tc2_Standard.REPLACE(Format, replaceString, Length, Tc2_Standard.FIND(Format, TimeType));]]> +ReplaceFormatPlaceholder := Tc2_Standard.REPLACE(format, replaceString, length, Tc2_Standard.FIND(format, timeType));]]> - _20210812-12:30:22:123_ + _20210812-12:30:22:123_ +{attribute 'hide_all_locals'} METHOD ToFormatString : Tc2_System.T_MaxString VAR_INPUT - Format : STRING; + /// Format the timestamp should have. + format : STRING; END_VAR VAR_INST inputString : STRING; @@ -167,8 +182,8 @@ VAR CONSTANT END_VAR ]]> - 0 DO char := Tc2_Standard.LEFT(inputString, 1); @@ -185,7 +200,7 @@ WHILE Tc2_Standard.LEN(inputString) > 0 DO inputString := Tc2_Standard.DELETE(inputString, 1, 1); END_WHILE -formattedString := ReplaceFormatPlaceholder(Format, year, 'Y'); +formattedString := ReplaceFormatPlaceholder(format, year, 'Y'); formattedString := ReplaceFormatPlaceholder(formattedString, month, 'M'); formattedString := ReplaceFormatPlaceholder(formattedString, day, 'D'); formattedString := ReplaceFormatPlaceholder(formattedString, hour, 'h'); diff --git a/TcLogProj/TcLog/Utils/POUs/DeleteOldFiles.TcPOU b/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU similarity index 95% rename from TcLogProj/TcLog/Utils/POUs/DeleteOldFiles.TcPOU rename to src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU index cc7a804..0b45490 100644 --- a/TcLogProj/TcLog/Utils/POUs/DeleteOldFiles.TcPOU +++ b/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU @@ -1,7 +1,8 @@  - - @@ -20,33 +20,33 @@ END_VAR +_fifo.A_AddTail(putValue := Tc2_Standard.CONCAT(text, '$N')); ]]> +_fifo.A_AddTail(putValue := text); ]]> - @@ -89,17 +89,18 @@ END_VAR - + - fileHandle); diff --git a/TcLogProj/TcLog/Utils/GenerateTimeData.TcPOU.bak b/src/TcLogProj/TcLog/Utils/GenerateTimeData.TcPOU.bak similarity index 100% rename from TcLogProj/TcLog/Utils/GenerateTimeData.TcPOU.bak rename to src/TcLogProj/TcLog/Utils/GenerateTimeData.TcPOU.bak diff --git a/TcLogProj/TcLog/Utils/POUs/GetFileAgeInSeconds.TcPOU b/src/TcLogProj/TcLog/Utils/GetFileAgeInSeconds.TcPOU similarity index 100% rename from TcLogProj/TcLog/Utils/POUs/GetFileAgeInSeconds.TcPOU rename to src/TcLogProj/TcLog/Utils/GetFileAgeInSeconds.TcPOU diff --git a/TcLogProj/TcLog/Utils/DataTypes/DeleteFilesState.TcDUT b/src/TcLogProj/TcLog/Utils/_DataTypes/DeleteFilesState.TcDUT similarity index 78% rename from TcLogProj/TcLog/Utils/DataTypes/DeleteFilesState.TcDUT rename to src/TcLogProj/TcLog/Utils/_DataTypes/DeleteFilesState.TcDUT index f57a2c2..fa1e25a 100644 --- a/TcLogProj/TcLog/Utils/DataTypes/DeleteFilesState.TcDUT +++ b/src/TcLogProj/TcLog/Utils/_DataTypes/DeleteFilesState.TcDUT @@ -1,7 +1,8 @@  - - - + @@ -25,7 +25,7 @@ ConvertAnyUSINTToString(); ConvertAnyWordToString(); ConvertAnyWStringToString();]]> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - TcLogTest Instance + + + TcLog Instance {08500001-0000-0000-F000-000000000064} @@ -32,18 +32,6 @@ - - - TcLog Instance - {08500001-0000-0000-F000-000000000064} - - - 1 - Default - - - - diff --git a/TcLogTest.NET/PersistingToFileSystem.cs b/src/TcLogTest.NET/PersistingToFileSystem.cs similarity index 98% rename from TcLogTest.NET/PersistingToFileSystem.cs rename to src/TcLogTest.NET/PersistingToFileSystem.cs index 9a73737..1336463 100644 --- a/TcLogTest.NET/PersistingToFileSystem.cs +++ b/src/TcLogTest.NET/PersistingToFileSystem.cs @@ -10,7 +10,7 @@ namespace TcLogTest.NET public class PersistingToFileSystem : IDisposable { PlcFixture fixture; - readonly string mut = "MAIN.TestWrapper"; + readonly string mut = "TESTS.TestWrapper"; readonly string path = "C:\\UnitTest\\"; readonly string filename = "UnitTest.txt"; readonly uint hPath; @@ -81,7 +81,7 @@ public async void Persist_simple_error_message() [Fact] public async void Persist_long_error_message() { - string message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquet orci sit amet massa placerat faucibus. Sed interdum fermentum eros. Maecenas accumsan rutrum ex, non varius orci scelerisque ac. Donec qui."; + string message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquet orci sit amet massa placerat faucibus. Sed interdum fermentum eros. Maecenas accumsan rutrum ex, non varius orci scelerisque ac. Donec qui"; uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Persist_long_error_message_run"); uint hData = fixture.TcClient.CreateVariableHandle(mut + ".Persist_long_error_message_data"); @@ -94,7 +94,7 @@ public async void Persist_long_error_message() Assert.Contains(message, fileContent[0]); Assert.Contains("Error", fileContent[0]); - foreach (var f in files) File.Delete(f); + //foreach (var f in files) File.Delete(f); fixture.TcClient.DeleteVariableHandle(hRun); fixture.TcClient.DeleteVariableHandle(hData); } diff --git a/TcLogTest.NET/PlcFixture.cs b/src/TcLogTest.NET/PlcFixture.cs similarity index 100% rename from TcLogTest.NET/PlcFixture.cs rename to src/TcLogTest.NET/PlcFixture.cs diff --git a/TcLogTest.NET/TcLogTest.NET.csproj b/src/TcLogTest.NET/TcLogTest.NET.csproj similarity index 100% rename from TcLogTest.NET/TcLogTest.NET.csproj rename to src/TcLogTest.NET/TcLogTest.NET.csproj