Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 1 of 1 total
Thread Using UDP Packets for Remote Logging
Thu, Aug 10 2006 11:11 AMPermanent Link

"Johnnie Norsworthy"
While playing around with some low level socket stuff recently, I came
across a new extremely helpful friend that I have been using for a couple of
weeks now. It is so very useful to me that I thought I'd share my technique
with other here.

Since my program uses the internet for a lot of its functionality, I can
assume that everyone using it has an internet connection. So I use their
connection to send me various events that happen throughout my software for
offsite debugging, performance tuning, and general use tracking.

I get a log entry when a user logs in that tells me their office, name, and
email. Then if I see a problem flagged later in my log, I know who to
contact. I also get log entries showing how long certain functions take,
such as loading data from the server into a DevEx Grid control, or any query
that I want to time. By flagging log entries with a category I can quickly
select which subset I want to view.

I run a simple little server program at my site using a dynamic DNS from
gotdns.org. So when my program starts at my customer site, it sees my
current server IP and uses that for the whole time they are connected. For
some strange reason, I picked port 12007 for sending the packets. SmileyMy
server program uses a DevEx Grid to allow me to search, sort and stuff like
that with a few controls buttons thrown in for some utility functions -
nothing too complicated.

The great thing about using UDP packets is that if your server is not
running, it doesn't cause any problems on the client machine. The program
just sends undeliverable packets. And since there is no handshaking, the
packets go out in less than a half of a second, causing no program slow
downs.

The packets I send have:
OfficeID,UserID,ProgramName,ProgramVersion,"Category" which I use to
identify specific functions, and "Entry" which is any text I want to send. I
just concatenate those together using "|" as a delimeter:

procedure TNetLog.LogEntry(Category, Entry: string);
begin
 // I simply disable logging if there is an error, which has never happened
 if Disabled then Exit;
 try
   WSocket.SendStr(OfficeID+'|'+UserID+'|'+ProgramFileName+'|'+ProgramFileVersion+'|'+
                  UpperCase(Category)+'|'+Entry);
  except
      Disabled := True;
  end;
end;

I have a datamodule with a single ICS TWSocket. In the datamodule create
event I set up the connection information and the program version and name
as fields. That is pretty much the entire code for sending packets using ICS
sockets.

procedure TNetLog.DataModuleCreate(Sender: TObject);
begin
 ProgramFileName := ExtractFileName(Application.ExeName);
 ProgramFileVersion :=
utilFileVersion.GetFileVersionBuild(Application.ExeName);
 Disabled := False;
 WSocket.Proto := 'udp';
 WSocket.Addr  := 'myconfiguredsite.gotdns.org';
 WSocket.Port  := '12007';
 try
   WSocket.Connect; // doesn't really connect, just sets up IP address from
name
 except
   Disabled := True;
 end;
end;

My server program socket code is equally as simple. I have one TWSocket
acting as a server called "Server" and a port opened and forwarded on my
router to the server machine. The whole program including DBISAM and DevEx
grids is about 4 meg. with probably less than 50 lines of real event code I
wrote. I have a "CoolTrayIcon" component to minimize to the system tray and
popup some balloon hints if enabled.

Here is my server socket event code. Some of my utility routines are thrown
in, but you can see the way it functions:

procedure TfLogServer.ServerDataAvailable(Sender: TObject; ErrCode: Word);
var
   Buffer : array [0..1023] of char;
   Len    : Integer;
   Src    : TSockAddrIn;
   SrcLen : Integer;
   s: string;
   IP,Office,User,Prog,Vers,Category,Entry,Email: String;
   pAt,pStart,pEnd,i: Integer;
begin
 SrcLen := SizeOf(Src);
 Len    := Server.ReceiveFrom(@Buffer, SizeOf(Buffer), Src, SrcLen);
 if Len<0 then Exit;
 Buffer[Len] := #0;
 s := StrPas(Buffer);
 try
   tableLOG.Append;
   IP := inet_ntoa(Src.sin_addr);
   Office     := StrDelimNum(s,'|',1);
   User       := StrDelimNum(s,'|',2);
   Prog       := StrDelimNum(s,'|',3);
   Vers       := StrDelimNum(s,'|',4);
   Category   := StrDelimNum(s,'|',5);
   Entry      := StrDelimNum(s,'|',6);
   // find the email address
   pAt := Pos('@',Entry);
   if pAt<>0 then
   begin
     i := pAt;
     while (i>1) and
           (not (Entry[i] in [' ','('])) do
       Dec(i);
     pStart := i+1;
     i := pAt;
     while (i<Length(Entry)) and
           (not (Entry[i] in [' ',')'])) do
       Inc(i);
     pEnd := i-1;
     Email := Copy(Entry,pStart,pEnd-pStart+2);
   end
   else
     Email := '';
   tableLOG.FieldByName('IP').AsString       := IP;
   tableLog.FieldByName('When').AsDateTime := Now;
   tableLOG.FieldByName('Office').AsString   := Office;
   tableLOG.FieldByName('User').AsString     := User;
   tableLOG.FieldByName('Program').AsString  := Prog;
   tableLOG.FieldByName('Version').AsString  := Vers;
   tableLOG.FieldByName('Category').AsString := Category;
   tableLOG.FieldByName('Entry').AsString    := Entry;
   tableLOG.FieldByName('Email').AsString    := Email;
   tableLOG.Post;
   if checkBalloon.Checked then
     if CoolTrayIcon.ShowBalloonHint(Office+':'+User+' -
'+Category,Entry,bitInfo,10) then ;
 except
   on E:Exception do
   begin
     memoLog.Lines.Add(s+' '+E.Message);
     tableLOG.Cancel;
   end;
 end;
end;

That's it. I am using this stuff so much now I thought it would be nice to
share. I was able to do all this in just a few hours and I find it
incredibly useful. Now I know what functions are really being used in my
program and can log timing on functions at customer sites. And when I talk
to people I can say, "it's about time you got to work".

-Johnnie

Image