diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 57a760a..dfd58e4 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,9 +18,10 @@ jobs: with: username: ${{ secrets.ACTIONS_ZGWK_USERNAME }} password: ${{ secrets.ACTIONS_ZGWK_PASSWORD }} - filepath: "src/TcLogProj/TcLog/TcLog.plcproj" + filepath: "." + doc-folder: "docs" - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} - publish_dir: archive/documentation/html + publish_dir: archive/docs/html diff --git a/README.md b/README.md index b18af47..45eb575 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ![](documentation/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.* It's usage is as simple as this: @@ -68,4 +69,4 @@ With [log4TC](https://mbc-engineering.github.io/log4TC/index.html) there is anot 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/). +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/documentation/userguide/getting_started.md b/documentation/userguide/getting_started.md index 4cc6586..d6eb99a 100644 --- a/documentation/userguide/getting_started.md +++ b/documentation/userguide/getting_started.md @@ -9,23 +9,23 @@ You would typically call `TcLogCore` once in your project and configure the logg Configure the core logger in your project: ``` VAR - _coreLogger : TcLogCore(bufferSize := 100 * SIZEOF(BYTE) * MAX_STRINGLENGTH); + _coreLogger : TcLogCore(bufferSize := 100 * SIZEOF(BYTE) * MAX_STRINGLENGTH); END_VAR _coreLogger - .WriteToAds() - .WriteToFile('c:\logs\', 'sensor_data.txt') - .MinimumLevel(LogLevels.Debug) - .RunLogger(); + .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; + _logger : TcLog; END_VAR -_logger.Debug('This is a debug message.'); -_logger.Error('This is an error message.'); +_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. diff --git a/documentation/userguide/logging.md b/documentation/userguide/logging.md index b216111..a862187 100644 --- a/documentation/userguide/logging.md +++ b/documentation/userguide/logging.md @@ -1,20 +1,22 @@ # Logging +Next, we will look at the logging options of TcLog. + ## 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); + _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(''); + .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") @@ -30,14 +32,14 @@ The most common use of logging will be in the form `IF ... THEN log() END_IF`. T ``` VAR _logger: TcLog; - _triggerLogging : R_TRIG; - _log : BOOL; + _triggerLogging : R_TRIG; + _log : BOOL; END_VAR _triggerLogging(CLK := _log); _logger - .OnCondition(_triggerLogging.Q) - .Error('Only logs when OnCondition evaluates to TRUE.'); + .OnCondition(_triggerLogging.Q) + .Error('Only logs when OnCondition evaluates to TRUE.'); ``` ## Logging on rising/falling edges @@ -45,13 +47,13 @@ Since a log message is usually to be sent once in the event of a *status change* ``` VAR - _loggerTrig : TcLogTRIG; + _loggerTrig : TcLogTRIG; _log : BOOL; END_VAR _loggerTrig - .OnRisingEdge(_log) - .Error('rTrig Test'); + .OnRisingEdge(_log) + .Error('rTrig Test'); ``` Likewise, logging can be triggered on falling edges with `OnFallingEdge(cond)`. @@ -61,24 +63,24 @@ Even though the logger was primarily designed as a singleton, it is possible to ``` VAR - _newLogger: TcLogCore; - _logger: TcLog; - _myInt : INT := 10; + _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(); - + .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(''); + .AppendAny(_myInt) + .Information(''); ``` From now on `_logger` considers the configuration of `_newLogger`. \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU b/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU index 4753d0a..bc3a28a 100644 --- a/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU +++ b/src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU @@ -23,9 +23,5 @@ END_CASE LogLevelToAdsLogMsgType := _retVal;]]> - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Logger/TcLog.TcPOU b/src/TcLogProj/TcLog/Logger/TcLog.TcPOU index 7f210de..a9ab2cf 100644 --- a/src/TcLogProj/TcLog/Logger/TcLog.TcPOU +++ b/src/TcLogProj/TcLog/Logger/TcLog.TcPOU @@ -348,65 +348,5 @@ END_IF _logDataInitialized := FALSE;]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU b/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU index 622ef4c..8bd8f04 100644 --- a/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU +++ b/src/TcLogProj/TcLog/Logger/TcLogCore.TcPOU @@ -12,24 +12,26 @@ /// automatically if enough memory is available. /// /// To set the inital buffer size to accomodate 100 messages, use it like this: -/// ``` +/// +/// ```st /// VAR /// myLogger: TcLogCore(BufferSize := 100 * SIZEOF(BYTE) * Tc2_System.MAX_STRING_LENGTH); /// END_VAR /// ``` +/// {attribute 'hide_all_locals'} FUNCTION_BLOCK TcLogCore IMPLEMENTS ILogCore VAR - _loggingSingleton : TcLog; // Logger-Singleton: Required to provide Singleton-functionality for TcLog. - _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 - _startLoggingConfiguration : BOOL; // Aux variable to provide fluent interface. - _internalErrorOccured : BOOL; // Aux variable to log internal errors to Error-property. - _config : LoggingConfiguration; // Logging configuration. Is set by invoking configuration methods. - _error : Error; // Contains information about internal error. - _busy : BOOL; // Logger is busy, typically with writing logs to the file system. + _loggingSingleton : TcLog; // Logger-Singleton: Required to provide Singleton-functionality for TcLog. + _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 + _startLoggingConfiguration : BOOL; // Aux variable to provide fluent interface. + _internalErrorOccured : BOOL; // Aux variable to log internal errors to Error-property. + _config : LoggingConfiguration; // Logging configuration. Is set by invoking configuration methods. + _error : Error; // Contains information about internal error. + _busy : BOOL; // Logger is busy, typically with writing logs to the file system. END_VAR ]]> @@ -44,17 +46,17 @@ METHOD PRIVATE BuildLoggingConfiguration : BOOL ]]> @@ -95,7 +97,7 @@ PROPERTY Configuration : LoggingConfiguration METHOD DeleteLogFilesAfterDays : REFERENCE TO TcLogCore VAR_INPUT /// Lifespan of logfiles in days. - lifespan : UINT; + lifespan : UINT; END_VAR ]]> @@ -121,9 +123,9 @@ PROPERTY Error : REFERENCE TO Error @@ -134,7 +136,7 @@ _logCache.Init(DINT_TO_UDINT(bufferSize));]]> @@ -169,16 +171,16 @@ IncludeInstancePath REF= This^;]]> METHOD LogCustomFormat : BOOL VAR_INPUT /// Data to be logged. - data : Tc2_System.T_MaxString; + data : Tc2_System.T_MaxString; END_VAR ]]> '' THEN - _logCache.AddLine(data); + _logCache.AddLine(data); END_IF]]> @@ -193,46 +195,46 @@ END_IF]]> METHOD LogStandardFormat : BOOL VAR_INPUT /// Data to be logged. - data : Tc2_System.T_MaxString; + data : Tc2_System.T_MaxString; /// Instance path of calling logger to locate source of logging message. - instancePath : Tc2_System.T_MaxString; + instancePath : Tc2_System.T_MaxString; /// Log level of message. - logLevel : LogLevels; + logLevel : LogLevels; END_VAR VAR_INST {attribute 'hide'} - messageBuilder : StringBuilder; + messageBuilder : StringBuilder; END_VAR ]]> = _config.MinimumLevel THEN - IF _config.WriteToAds THEN - Tc2_System.ADSLOGSTR( - ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(logLevel), - messageBuilder - .Reset() - .AppendIf(_config.IncludeInstancePath, instancePath) - .AppendIf(_config.IncludeInstancePath, _config.Delimiter) - .Append(data) - .ToString(), - '' - ); - END_IF - - IF _config.FileName <> '' THEN - _logCache.AddLine( - messageBuilder - .Reset() - .Append(_localTimeAsString) - .Append(_config.Delimiter) - .Append(TO_STRING(LogLevel)) - .AppendIf(_config.IncludeInstancePath, _config.Delimiter) - .AppendIf(_config.IncludeInstancePath, instancePath) - .Append(_config.Delimiter) - .Append(data) - .ToString() - ); - END_IF + IF _config.WriteToAds THEN + Tc2_System.ADSLOGSTR( + ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(logLevel), + messageBuilder + .Reset() + .AppendIf(_config.IncludeInstancePath, instancePath) + .AppendIf(_config.IncludeInstancePath, _config.Delimiter) + .Append(data) + .ToString(), + '' + ); + END_IF + + IF _config.FileName <> '' THEN + _logCache.AddLine( + messageBuilder + .Reset() + .Append(_localTimeAsString) + .Append(_config.Delimiter) + .Append(TO_STRING(LogLevel)) + .AppendIf(_config.IncludeInstancePath, _config.Delimiter) + .AppendIf(_config.IncludeInstancePath, instancePath) + .Append(_config.Delimiter) + .Append(data) + .ToString() + ); + END_IF END_IF ]]> @@ -244,7 +246,7 @@ END_IF METHOD MinimumLevel : REFERENCE TO TcLogCore VAR_INPUT /// Minimum log level to be logged. - level : LogLevels; + level : LogLevels; END_VAR ]]> @@ -261,30 +263,30 @@ MinimumLevel REF=THIS^;]]> {attribute 'hide_all_locals'} METHOD PRIVATE RollingIntervalReached : BOOL VAR_INPUT - currentUtcTime : Tc2_Utilities.TIMESTRUCT; // Current system time as utc - rollingInterval : RollingIntervals; // Rolling interval + currentUtcTime : Tc2_Utilities.TIMESTRUCT; // Current system time as utc + rollingInterval : RollingIntervals; // Rolling interval END_VAR VAR_INST - newHour : Tc2_Standard.R_TRIG; - newDay : Tc2_Standard.R_TRIG; - newMonth : Tc2_Standard.R_TRIG; - - hourRolled : BOOL; - dayRolled : BOOL; - monthRolled : BOOL; + newHour : Tc2_Standard.R_TRIG; + newDay : Tc2_Standard.R_TRIG; + newMonth : Tc2_Standard.R_TRIG; + + hourRolled : BOOL; + dayRolled : BOOL; + monthRolled : BOOL; END_VAR ]]> hourRolled); + Q => hourRolled); newDay(CLK := (hourRolled AND currentUtcTime.wHour = 0), - Q => dayRolled); + Q => dayRolled); newMonth(CLK := (dayRolled AND currentUtcTime.wDay = 1), - Q => monthRolled); + 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); ]]> @@ -297,11 +299,11 @@ RollingIntervalReached := (hourRolled AND rollingInterval = RollingIntervals {attribute 'hide_all_locals'} METHOD RunLogger : BOOL VAR_INST - newDay : Tc2_Standard.R_TRIG; - dateTime : DateTime; - deleteExpiredLogFiles : DeleteOldFiles; - fileNameStringBuilder : StringBuilder; - timeStamp : STRING; // Timestamp used for filename + newDay : Tc2_Standard.R_TRIG; + dateTime : DateTime; + deleteExpiredLogFiles : DeleteOldFiles; + fileNameStringBuilder : StringBuilder; + timeStamp : STRING; // Timestamp used for filename END_VAR ]]> @@ -314,36 +316,36 @@ _utcTimeAsFileTime := dateTime.AsUtcFileTime; // Generate new timestamp for filename IF RollingIntervalReached(_localTimeAsSystemTime, _config.RollingInterval) OR dateTime.Done THEN - timeStamp := dateTime.ToFormatString(_config.TimestampFormat); + timeStamp := dateTime.ToFormatString(_config.TimestampFormat); END_IF // Delete expired log files newDay(CLK := _localTimeAsSystemTime.wHour = 0); deleteExpiredLogFiles( - StartScan := newDay.Q AND (_config.LogFileLifespan > 0), - ExpirationInDays := _config.LogFileLifespan, - FilePath := _config.FilePath, - FileName := _config.FileName, - CurrentUtcTime := _utcTimeAsFileTime - ); + StartScan := newDay.Q AND (_config.LogFileLifespan > 0), + ExpirationInDays := _config.LogFileLifespan, + FilePath := _config.FilePath, + FileName := _config.FileName, + CurrentUtcTime := _utcTimeAsFileTime + ); IF deleteExpiredLogFiles.Error.Active THEN - _internalErrorOccured := TRUE; - _error := deleteExpiredLogFiles.Error; + _internalErrorOccured := TRUE; + _error := deleteExpiredLogFiles.Error; END_IF - + // Persist logging messages to file _logCache.PersistToFile( - fileNameStringBuilder - .Reset() - .Append(_config.FilePath) - .AppendIf(Tc2_Standard.RIGHT(_config.FilePath, 1) <> '\', '\') - .Append(timeStamp) - .Append(_config.FileName) - .ToString() + fileNameStringBuilder + .Reset() + .Append(_config.FilePath) + .AppendIf(Tc2_Standard.RIGHT(_config.FilePath, 1) <> '\', '\') + .Append(timeStamp) + .Append(_config.FileName) + .ToString() ); IF _logCache.Error.Active THEN - _internalErrorOccured := TRUE; - _error := _logCache.Error; + _internalErrorOccured := TRUE; + _error := _logCache.Error; END_IF // Forward busy state of LogCache @@ -354,9 +356,9 @@ _startLoggingConfiguration := FALSE; // Reset error flag each cycle to automatically confirm errors once they are gone. IF NOT _internalErrorOccured THEN - _error.Active := FALSE; - _error.Code := ErrorCodes.None; - _error.Info := ''; + _error.Active := FALSE; + _error.Code := ErrorCodes.None; + _error.Info := ''; END_IF _internalErrorOccured := FALSE;]]> @@ -367,7 +369,7 @@ _internalErrorOccured := FALSE;]]> METHOD SetDelimiter : REFERENCE TO TcLogCore VAR_INPUT /// Delimiter between different columns of log file. - delimiter : STRING(1); + delimiter : STRING(1); END_VAR ]]> @@ -385,7 +387,7 @@ SetDelimiter REF=THIS^;]]> METHOD SetRollingInterval : REFERENCE TO TcLogCore VAR_INPUT /// Rolling interval for log file. Once exceeded, a new log file will be created. - interval : RollingIntervals; + interval : RollingIntervals; END_VAR ]]> @@ -401,8 +403,8 @@ SetRollingInterval REF= THIS^;]]> /// Sets the format of the timestamp in the file name. /// /// The following format options are available: -/// Y : year -/// M : month +/// Y : year +/// M : month /// D : day /// h : hour /// m : minute @@ -414,7 +416,7 @@ SetRollingInterval REF= THIS^;]]> METHOD TimestampFormat : REFERENCE TO TcLogCore VAR_INPUT /// Format the timestamp should have. - format : STRING; + format : STRING; END_VAR ]]> @@ -444,9 +446,9 @@ WriteToAds REF= This^;]]> METHOD WriteToFile : REFERENCE TO TcLogCore VAR_INPUT /// File path the logs will be written to. - path : Tc2_System.T_MaxString; + path : Tc2_System.T_MaxString; /// File name of the log file. Will automatically be prefixed by timestamp. - fileName : STRING; + fileName : STRING; END_VAR ]]> @@ -462,94 +464,5 @@ _config.FileName := fileName; WriteToFile REF= This^;]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Logger/_Tests/TestWrapper_NET.TcPOU b/src/TcLogProj/TcLog/Logger/_Tests/TestWrapper_NET.TcPOU index c82e580..4d1cf7b 100644 --- a/src/TcLogProj/TcLog/Logger/_Tests/TestWrapper_NET.TcPOU +++ b/src/TcLogProj/TcLog/Logger/_Tests/TestWrapper_NET.TcPOU @@ -198,11 +198,5 @@ END_IF ]]> - - - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU b/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU index 24f97cc..d9a185d 100644 --- a/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU +++ b/src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU @@ -87,31 +87,5 @@ Reset := THIS^;]]> - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/TESTS.TcPOU b/src/TcLogProj/TcLog/TESTS.TcPOU index 5fb77cf..7477788 100644 --- a/src/TcLogProj/TcLog/TESTS.TcPOU +++ b/src/TcLogProj/TcLog/TESTS.TcPOU @@ -14,9 +14,5 @@ END_VAR TestWrapper();]]> - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU b/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU index fb00e63..acc56c3 100644 --- a/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU +++ b/src/TcLogProj/TcLog/Utils/AnyToString.TcPOU @@ -58,9 +58,5 @@ ELSE AnyToString := ANY_TO_STRING(data.pValue^); END_IF]]> - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Utils/DateTime.TcPOU b/src/TcLogProj/TcLog/Utils/DateTime.TcPOU index a34ce09..1414d6a 100644 --- a/src/TcLogProj/TcLog/Utils/DateTime.TcPOU +++ b/src/TcLogProj/TcLog/Utils/DateTime.TcPOU @@ -214,40 +214,5 @@ ToFormatString := formattedString; ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU b/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU index 9775706..6fd0fa9 100644 --- a/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU +++ b/src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU @@ -146,9 +146,5 @@ IF _fileDelete.bError THEN Error.Info := Tc2_Standard.CONCAT('Deleting expired log file in specified directory failed. Error thrown by FB_FileDelete. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(_fileDelete.nErrId)); END_IF]]> - - - - \ No newline at end of file diff --git a/src/TcLogProj/TcLog/Utils/DynamicStringBuffer.TcPOU b/src/TcLogProj/TcLog/Utils/DynamicStringBuffer.TcPOU index 09e4816..3eaa2ae 100644 --- a/src/TcLogProj/TcLog/Utils/DynamicStringBuffer.TcPOU +++ b/src/TcLogProj/TcLog/Utils/DynamicStringBuffer.TcPOU @@ -288,47 +288,5 @@ IF requiredSize > _fifo.cbBuffer THEN END_IF]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file