Re: Challenge: Build a Color-Coded Log Viewer for Phix/Euphoria (Inspired by chat.exw)
- Posted by xecronix 2 weeks ago
- 419 views
I have completed the happy path for the logger. The Pascal code is shown below. If you're working on a live stream log viewer, the important bits to note about the logger are:
This is the message format. Msg can have newlines. So I made an <EOM> End Of Message delimiter. It's configurable. (see config file below)
FullMsg := '[' + LevelStr + '] [' + FormatTimestamp + '] ' + Msg;
This is how data moves across the wire. LineEnding is configured to be platform specific.
Data := Payload + LineEnding + MessageTerminator + LineEnding; Sock.SendString(Data);
I tightened this up a little bit. There are more details, but this shows the log levels and their strings in the correct order.
case ALevel of llSilent: ; // no output llError: LogLine('ERROR', Msg); llWarning: LogLine('WARNING', Msg); llInfo: LogLine('INFO', Msg); llDebug: LogLine('DEBUG', Msg); llVerbose: LogLine('VERBOSE', Msg); end;
This is an example config file.
[viewer] in_log_file=bzlog-sample.log port=9595 out_log_file=viewer-output.log socket_timeout_ms=30000 message_terminator=MAGIC$sdfgsdfgsdfgEND_OF_MESSAGE [client] viewer_ip=127.0.0.1 viewer_port=9595 message_terminator=MAGIC$sdfgsdfgsdfgEND_OF_MESSAGE fallback_log_file=bzlog-sample.log
Full Pascal source. This is subject to change, but it should be close enough for you if you're working on a live log viewer.
unit bzlogger; {$mode objfpc}{$H+} interface uses Classes, SysUtils; type TLogLevel = (llSilent, llError, llWarning, llInfo, llDebug, llVerbose); procedure InitLogger(const AConfigFile: string = 'bzlog.conf'); procedure CloseLogger; procedure Logger(ALevel: TLogLevel; const Msg: string); procedure SetLogLevel(ALevel: TLogLevel); const SILENT = llSilent; ERROR = llError; WARNING = llWarning; INFO = llInfo; DEBUG = llDebug; VERBOSE = llVerbose; implementation uses IniFiles, blcksock, synautil; var LogFile: TextFile; LogFileOpen: Boolean = False; LogFileName: string = 'bzscript.log'; CurrentLogLevel: TLogLevel = llVerbose; ViewerIP: string = '127.0.0.1'; ViewerPort: Word = 9595; MessageTerminator: string = '<EOL>'; FallbackLogFile: string = 'bzscript.log'; ConfLoaded: Boolean = False; procedure LoadConfig(const AConfigFile: string); var Ini: TIniFile; begin Ini := TIniFile.Create(AConfigFile); try ViewerIP := Ini.ReadString('client', 'viewer_ip', '127.0.0.1'); ViewerPort := Ini.ReadInteger('client', 'viewer_port', 9595); MessageTerminator:= Ini.ReadString('client', 'message_terminator', '<EOM>'); FallbackLogFile := Ini.ReadString('client', 'fallback_log_file', 'bzscript.log'); finally Ini.Free; end; ConfLoaded := True; end; procedure InitLogger(const AConfigFile: string); begin LoadConfig(AConfigFile); LogFileName := FallbackLogFile; AssignFile(LogFile, LogFileName); if FileExists(LogFileName) then Append(LogFile) else Rewrite(LogFile); LogFileOpen := True; end; procedure CloseLogger; begin if LogFileOpen then begin CloseFile(LogFile); LogFileOpen := False; end; end; procedure SetLogLevel(ALevel: TLogLevel); begin CurrentLogLevel := ALevel; end; function FormatTimestamp: string; begin Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now); end; procedure WriteToFile(const LevelStr, Msg: string); begin if not LogFileOpen then InitLogger(''); WriteLn(LogFile, '[' + LevelStr + '] [' + FormatTimestamp + '] ' + Msg); Flush(LogFile); end; function SendLogOverTcp(const Payload: string): Boolean; var Sock: TTCPBlockSocket; Data: string; begin Result := False; Sock := TTCPBlockSocket.Create; try Sock.Connect(ViewerIP, IntToStr(ViewerPort)); if Sock.LastError = 0 then begin Data := Payload + LineEnding + MessageTerminator + LineEnding; Sock.SendString(Data); if Sock.LastError = 0 then Result := True else WriteToFile('TCP_FAIL', 'Send error: ' + Sock.LastErrorDesc); end else WriteToFile('TCP_FAIL', 'Connect error: ' + Sock.LastErrorDesc); finally Sock.Free; end; end; procedure LogLine(const LevelStr, Msg: string); var FullMsg: string; begin FullMsg := '[' + LevelStr + '] [' + FormatTimestamp + '] ' + Msg; if not ConfLoaded then InitLogger(''); if not SendLogOverTcp(FullMsg) then WriteToFile(LevelStr, Msg); end; procedure Logger(ALevel: TLogLevel; const Msg: string); begin if ALevel > CurrentLogLevel then Exit; case ALevel of llSilent: ; // no output llError: LogLine('ERROR', Msg); llWarning: LogLine('WARNING', Msg); llInfo: LogLine('INFO', Msg); llDebug: LogLine('DEBUG', Msg); llVerbose: LogLine('VERBOSE', Msg); end; end; end.
My next steps.
- try and find someone to do a code review.
- write more tests. So far I have 3 passing tests. Probably a little weak to be honest.
- export the code as a dll/so. At the moment it's just a lib. (called units in pascal)
- make a Euphoria wrapper for the dll.
- if Phix wraps up differently, make a wrapper for that too.
- get this up on github.