Re: Challenge: Build a Color-Coded Log Viewer for Phix/Euphoria (Inspired by chat.exw)

new topic     » goto parent     » topic index » view thread      » older message » newer message

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.

  1. try and find someone to do a code review.
  2. write more tests. So far I have 3 passing tests. Probably a little weak to be honest.
  3. export the code as a dll/so. At the moment it's just a lib. (called units in pascal)
  4. make a Euphoria wrapper for the dll.
  5. if Phix wraps up differently, make a wrapper for that too.
  6. get this up on github.
new topic     » goto parent     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu