Re: Challenge: Build a Color-Coded Log Viewer for Phix/Euphoria (Inspired by chat.exw)
- Posted by xecronix in June
- 949 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.

