Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Added support for external log handlers to AWS
From 0834478f52d58f60d702a636df7b5db2807e9fd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20L=C3=B8cke?= <thomas@12boo.net>
Date: Tue, 8 Nov 2011 16:47:34 +0100
Subject: [PATCH] Added support for external log handlers.
The primary goal is to be able to let something like syslog handle the AWS log
data, but it can also be used for adding log data to a database or what else
you can come up with.
The important thing is to be able to relieve AWS from it's log writing duties.
---
src/core/aws-log.adb | 205 ++++++++++++++++++++++++++++++++++++++-----
src/core/aws-log.ads | 53 +++++++++---
src/core/aws-server-log.adb | 42 +++++++++
src/core/aws-server-log.ads | 31 ++++++-
4 files changed, 296 insertions(+), 35 deletions(-)
diff --git a/src/core/aws-log.adb b/src/core/aws-log.adb
index e6ff956..7eeaa30 100644
--- a/src/core/aws-log.adb
+++ b/src/core/aws-log.adb
@@ -30,6 +30,7 @@ with Ada.Command_Line;
with Ada.Directories;
with Ada.Strings.Fixed;
with Ada.Strings.Maps;
+with Ada.Strings.Unbounded;
with Ada.Text_IO.C_Streams;
with GNAT.Calendar.Time_IO;
@@ -39,6 +40,16 @@ package body AWS.Log is
procedure Check_Split (Log : in out Object; Now : Ada.Calendar.Time);
-- Split log file if necessary
+ procedure Write_External (Log : in out Object; Data : in out Fields_Table);
+ -- Write extended format record to log file and prepare record for the next
+ -- data. It is not allowed to use same Fields_Table with different extended
+ -- logs.
+
+ procedure Write_File (Log : in out Object; Data : in out Fields_Table);
+ -- Write extended format record to log file and prepare record for the next
+ -- data. It is not allowed to use same Fields_Table with different extended
+ -- logs.
+
procedure Write_Log
(Log : in out Object;
Now : Calendar.Time;
@@ -101,6 +112,8 @@ package body AWS.Log is
begin
if Text_IO.Is_Open (Log.File) then
return Text_IO.Name (Log.File);
+ elsif Log.External_Writer /= null then
+ return To_String (Log.External_Writer_Name);
else
return "";
end if;
@@ -122,7 +135,7 @@ package body AWS.Log is
procedure Flush (Log : in out Object) is
use Text_IO;
begin
- if Log.Auto_Flush then
+ if Log.External_Writer /= null or else Log.Auto_Flush then
return;
end if;
@@ -156,7 +169,7 @@ package body AWS.Log is
function Is_Active (Log : Object) return Boolean is
begin
- return Text_IO.Is_Open (Log.File);
+ return Text_IO.Is_Open (Log.File) or else Log.External_Writer /= null;
end Is_Active;
----------
@@ -387,16 +400,39 @@ package body AWS.Log is
Text_IO.Create (Log.File, Text_IO.Out_File, To_String (Filename));
end Start;
+ -----------
+ -- Start --
+ -----------
+
+ procedure Start
+ (Log : in out Object;
+ External_Writer : External_Writer_Ptr;
+ Name : String)
+ is
+ begin
+ Log.External_Writer := External_Writer;
+ Log.External_Writer_Name := To_Unbounded_String (Name);
+ Log.Header_Written := False;
+ end Start;
+
----------
-- Stop --
----------
procedure Stop (Log : in out Object) is
begin
- if Text_IO.Is_Open (Log.File) then
- Write (Log, "Stop logging.");
- Text_IO.Close (Log.File);
+ if not Log.Stop_Has_Been_Called then
+ if Log.External_Writer = null then
+ if Text_IO.Is_Open (Log.File) then
+ Write (Log, "Stop logging.");
+ Text_IO.Close (Log.File);
+ end if;
+ else
+ Log.External_Writer ("Stop logging.");
+ end if;
end if;
+
+ Log.Stop_Has_Been_Called := True;
end Stop;
-----------
@@ -471,27 +507,35 @@ package body AWS.Log is
end if;
end Authorization_Name;
+ Log_Message : constant String := AWS.Status.Peername (Connect_Stat)
+ & " - " & Authorization_Name
+ & " ["
+ & GNAT.Calendar.Time_IO.Image (Now, "%d/%b/%Y:%T")
+ & "] """
+ & Status.Method (Connect_Stat)
+ & ' '
+ & Status.URI (Connect_Stat) & " "
+ & Status.HTTP_Version (Connect_Stat) & """ "
+ & Data;
+
begin
- Write_Log
- (Log, Now,
- AWS.Status.Peername (Connect_Stat)
- & " - " & Authorization_Name
- & " ["
- & GNAT.Calendar.Time_IO.Image (Now, "%d/%b/%Y:%T")
- & "] """
- & Status.Method (Connect_Stat)
- & ' '
- & Status.URI (Connect_Stat) & " "
- & Status.HTTP_Version (Connect_Stat) & """ "
- & Data);
+ if Log.External_Writer = null then
+ Write_Log (Log, Now, Log_Message);
+ else
+ Log.External_Writer (Log_Message);
+ end if;
end Write;
procedure Write (Log : in out Object; Data : String) is
- Now : constant Calendar.Time := Calendar.Clock;
+ Now : constant Calendar.Time := Calendar.Clock;
+ Log_Message : constant String :=
+ "[" & GNAT.Calendar.Time_IO.Image (Now, "%d/%b/%Y:%T") & "] " & Data;
begin
- Write_Log
- (Log, Now,
- "[" & GNAT.Calendar.Time_IO.Image (Now, "%d/%b/%Y:%T") & "] " & Data);
+ if Log.External_Writer = null then
+ Write_Log (Log, Now, Log_Message);
+ else
+ Log.External_Writer (Log_Message);
+ end if;
end Write;
-- Here is the extended log format:
@@ -502,6 +546,122 @@ package body AWS.Log is
-- 00:34:23 GET /foo/bar.html
procedure Write (Log : in out Object; Data : in out Fields_Table) is
+ begin
+ if Log.External_Writer = null then
+ Write_File (Log, Data);
+ else
+ Write_External (Log, Data);
+ end if;
+ end Write;
+
+ ----------------------
+ -- Write_External --
+ ----------------------
+
+ procedure Write_External
+ (Log : in out Object; Data : in out Fields_Table) is
+ use Ada.Strings.Unbounded;
+ use GNAT.Calendar.Time_IO;
+
+ Length : constant Natural := Natural (Log.Extended_Fields.Length);
+ Message : Unbounded_String := Null_Unbounded_String;
+ Now : Ada.Calendar.Time;
+ First_Field : Boolean := True;
+
+ procedure Write_And_Clear (Position : SV.Cursor);
+
+ ---------------------
+ -- Write_And_Clear --
+ ---------------------
+
+ procedure Write_And_Clear (Position : SV.Cursor) is
+ begin
+ if First_Field then
+ First_Field := False;
+ Append (Message, SV.Element (Position));
+ else
+ Append (Message, ' ' & SV.Element (Position));
+ end if;
+
+ Data.Values.Replace_Element (Position, "-");
+ end Write_And_Clear;
+
+ begin
+ if Length = 0 then
+ -- It is not extended log
+ return;
+ end if;
+
+ Now := Ada.Calendar.Clock;
+
+ if not Log.Header_Written then
+ Log.Header_Written := True;
+
+ Log.External_Writer ("#Version: 1.0");
+ Log.External_Writer ("#Software: AWS (Ada Web Server) v" & Version);
+ Log.External_Writer ("#Date: " & Image (Now, ISO_Date & " %T"));
+ Log.External_Writer ("#Fields:");
+
+ declare
+ Fields : Unbounded_String := Null_Unbounded_String;
+ Order : array (1 .. Length) of Strings_Positive.Cursor;
+
+ procedure Process (Position : Strings_Positive.Cursor);
+
+ -------------
+ -- Process --
+ -------------
+
+ procedure Process (Position : Strings_Positive.Cursor) is
+ begin
+ Order (Strings_Positive.Element (Position)) := Position;
+ end Process;
+
+ begin
+ Log.Extended_Fields.Iterate (Process'Access);
+
+ for J in Order'Range loop
+ Append (Fields, ' ' & Strings_Positive.Key (Order (J)));
+ end loop;
+
+ Log.External_Writer (To_String (Fields));
+ end;
+ end if;
+
+ -- Set date and time fields if the used does not fill it
+
+ declare
+ CSN : Strings_Positive.Cursor := Log.Extended_Fields.Find ("date");
+ P : Positive;
+ begin
+ if Strings_Positive.Has_Element (CSN) then
+ P := Strings_Positive.Element (CSN);
+
+ if Data.Values.Element (P) = "-" then
+ Data.Values.Replace_Element (P, Image (Now, ISO_Date));
+ end if;
+ end if;
+
+ CSN := Log.Extended_Fields.Find ("time");
+
+ if Strings_Positive.Has_Element (CSN) then
+ P := Strings_Positive.Element (CSN);
+
+ if Data.Values.Element (P) = "-" then
+ Data.Values.Replace_Element (P, Image (Now, "%T"));
+ end if;
+ end if;
+ end;
+
+ Data.Values.Iterate (Write_And_Clear'Access);
+ Log.External_Writer (To_String (Message));
+ end Write_External;
+
+ ------------------
+ -- Write_File --
+ ------------------
+
+ procedure Write_File (Log : in out Object; Data : in out Fields_Table) is
use GNAT.Calendar.Time_IO;
Length : constant Natural := Natural (Log.Extended_Fields.Length);
@@ -527,6 +687,7 @@ package body AWS.Log is
end Write_And_Clear;
begin
+
if Length = 0 then
-- It is not extended log
return;
@@ -615,7 +776,7 @@ package body AWS.Log is
when others =>
Log.Semaphore.Release;
raise;
- end Write;
+ end Write_File;
---------------
-- Write_Log --
diff --git a/src/core/aws-log.ads b/src/core/aws-log.ads
index 9dc41d1..ec945fd 100644
--- a/src/core/aws-log.ads
+++ b/src/core/aws-log.ads
@@ -53,6 +53,15 @@ package AWS.Log is
type Object is limited private;
-- A log object. It must be activated by calling Start below
+ type External_Writer_Ptr is access procedure (Message : String);
+ -- Access to a procedure that handles AWS access and/or error log data.
+ -- If the access and/or error logs are started with an external writer
+ -- procedure set, then AWS will no longer handle writing the log data
+ -- to file, nor will it rotate or split the data. In short: If you set
+ -- an external writer, it's up to you to handle these things.
+ -- The raw log data generated by AWS is simply handed verbatim to the
+ -- external writer procedure.
+
type Split_Mode is (None, Each_Run, Daily, Monthly);
-- It specifies when to create a new log file.
-- None : all log info gets accumulated into the same file.
@@ -83,6 +92,19 @@ package AWS.Log is
-- for logs with few entries per second as the flush has a performance
-- penalty.
+ procedure Start
+ (Log : in out Object;
+ External_Writer : External_Writer_Ptr;
+ Name : String);
+ -- Activate server's activity logging and send all log data to External
+ -- Writer. When the logging object is started with an External_Writer no
+ -- splitting or size limits are imposed on the logging data. This will all
+ -- have to be handled in the External_Writer.
+ -- When a log is started with an external writer, all log data is passed
+ -- verbatim to the registered procedure.
+ -- The Name String is returned when the Filename function is called. This
+ -- serves no other function than to label the External_Writer procedure.
+
procedure Register_Field (Log : in out Object; Id : String);
-- Register field to be written into extended log format
@@ -137,6 +159,8 @@ package AWS.Log is
procedure Flush (Log : in out Object);
-- Flush the data to the Log file, for be able to see last logged
-- messages.
+ -- If an external writer procedure is used to handle the log data, then
+ -- calling this procedure will do nothing.
procedure Stop (Log : in out Object);
-- Stop logging activity
@@ -147,9 +171,13 @@ package AWS.Log is
function Filename (Log : Object) return String;
-- Returns current log filename or the empty string if the log is not
-- activated.
+ -- If an external writer is used to handle the log, then the name given in
+ -- the Start procedure is returned. See the Start procedure for starting
+ -- logs with an External_Writer.
function Mode (Log : Object) return Split_Mode;
- -- Returns the split mode. None will be returned if log is not activated
+ -- Returns the split mode. None will be returned if log is not activated or
+ -- an external writer procedure is used to handle the log data.
private
@@ -170,16 +198,19 @@ private
Not_Specified : constant String := "";
type Object is new Ada.Finalization.Limited_Controlled with record
- File : Text_IO.File_Type;
- Extended_Fields : Strings_Positive.Map;
- Header_Written : Boolean;
- File_Directory : Unbounded_String;
- Filename_Prefix : Unbounded_String;
- Split : Split_Mode := None;
- Size_Limit : Natural := 0;
- Current_Tag : Positive;
- Semaphore : Utils.Semaphore;
- Auto_Flush : Boolean;
+ File : Text_IO.File_Type;
+ External_Writer : External_Writer_Ptr := null;
+ External_Writer_Name : Unbounded_String := Null_Unbounded_String;
+ Stop_Has_Been_Called : Boolean := False;
+ Extended_Fields : Strings_Positive.Map;
+ Header_Written : Boolean;
+ File_Directory : Unbounded_String;
+ Filename_Prefix : Unbounded_String;
+ Split : Split_Mode := None;
+ Size_Limit : Natural := 0;
+ Current_Tag : Positive;
+ Semaphore : Utils.Semaphore;
+ Auto_Flush : Boolean;
end record;
overriding procedure Finalize (Log : in out Object);
diff --git a/src/core/aws-server-log.adb b/src/core/aws-server-log.adb
index 8825ac1..ae1a98c 100644
--- a/src/core/aws-server-log.adb
+++ b/src/core/aws-server-log.adb
@@ -122,6 +122,35 @@ package body AWS.Server.Log is
Auto_Flush => Auto_Flush);
end Start;
+ -----------
+ -- Start --
+ -----------
+
+ procedure Start
+ (Web_Server : in out HTTP;
+ External_Writer : AWS.Log.External_Writer_Ptr;
+ Name : String)
+ is
+ procedure Register_Extended_Field (Id : String);
+
+ -----------------------------
+ -- Register_Extended_Field --
+ -----------------------------
+
+ procedure Register_Extended_Field (Id : String) is
+ begin
+ AWS.Log.Register_Field (Web_Server.Log, Id);
+ end Register_Extended_Field;
+
+ procedure Register_Extended_Fields is
+ new CNF.Log_Extended_Fields_Generic_Iterate (Register_Extended_Field);
+
+ begin
+ Register_Extended_Fields (Web_Server.Properties);
+
+ AWS.Log.Start (Web_Server.Log, External_Writer, Name);
+ end Start;
+
-----------------
-- Start_Error --
-----------------
@@ -154,6 +183,19 @@ package body AWS.Server.Log is
Auto_Flush => True);
end Start_Error;
+ -----------------
+ -- Start_Error --
+ -----------------
+
+ procedure Start_Error
+ (Web_Server : in out HTTP;
+ External_Writer : AWS.Log.External_Writer_Ptr;
+ Name : String)
+ is
+ begin
+ AWS.Log.Start (Web_Server.Error_Log, External_Writer, Name);
+ end Start_Error;
+
----------
-- Stop --
----------
diff --git a/src/core/aws-server-log.ads b/src/core/aws-server-log.ads
index 1ef9982..a578062 100644
--- a/src/core/aws-server-log.ads
+++ b/src/core/aws-server-log.ads
@@ -24,6 +24,7 @@
-- however invalidate any other reasons why the executable file might be --
-- covered by the GNU Public License. --
------------------------------------------------------------------------------
+with AWS.Log;
package AWS.Server.Log is
@@ -39,8 +40,21 @@ package AWS.Server.Log is
-- Activate server's logging activity. See AWS.Log. If Auto_Flush is True
-- the file will be flushed after all written data.
+ procedure Start
+ (Web_Server : in out HTTP;
+ External_Writer : AWS.Log.External_Writer_Ptr;
+ Name : String);
+ -- Activate the Web_Server access log and direct all data to the
+ -- External_Writer.
+ -- The Name String is returned when the Name function is called. It is a
+ -- simple identifier, that serves no other purpose than to give the
+ -- external writer a label.
+
function Name (Web_Server : HTTP) return String;
- -- Return the name of the Log or an empty string if one is not active
+ -- Return the name of the Log or an empty string if one is not active. If
+ -- an external writer is used to handle the access log, then the name of
+ -- that writer is returned. See the Start procedure for starting the access
+ -- log with an External_Writer.
procedure Stop (Web_Server : in out HTTP);
-- Stop server's logging activity. See AWS.Log
@@ -63,8 +77,21 @@ package AWS.Server.Log is
Filename_Prefix : String := "");
-- Activate server's logging activity. See AWS.Log
+ procedure Start_Error
+ (Web_Server : in out HTTP;
+ External_Writer : AWS.Log.External_Writer_Ptr;
+ Name : String);
+ -- Activate the Web_Server error log and direct all data to the
+ -- External_Writer.
+ -- The Name String is returned when the Error_Name function is called. It
+ -- is a simple identifier, that serves no other purpose than to give the
+ -- external writer a label.
+
function Error_Name (Web_Server : HTTP) return String;
- -- Return the name of the Error Log or an empty string if one is not active
+ -- Return the name of the Error Log or an empty string if one is not
+ -- active. If an external writer is used to handle the error log, then
+ -- the name of that writer is returned. See the Start_Error procedure
+ -- for starting the error log with an External_Writer.
procedure Stop_Error (Web_Server : in out HTTP);
-- Stop server's logging activity. See AWS.Log
--
1.7.4.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment