The following PHP class can be used to generate machine-parsable log files that include an arbitrary number of entries per request. It correlates the request time, session, action, and location with any number of events generated by the business logic of an application, and recognizes three levels of log messages: debug messages (which would be ignored by produc- tion servers), regular log messages, and alert messages (which are copied to an email address).
This code can be found also as ]`XXVc4]RddaYa in the Chapter 20 folder of the downloadable archive of code for Pro PHP Security at Yeea+ hhhRacVddT`^.
-0aYa
T]Rdd]`XXVcl Z_WcRdecfTefcV
acZgReVaReY, aReY`W]`XWZ]V
acZgReVSfWWVc.RccRj, RccRje`Y`]U]`XV_ecZVd
acZgReVUVSfX.72=D6, TRaefcVUVSfX]VgV]^VddRXVdZWUVdZcVU acZgReVR]VceE`, V^RZ]e`dV_UR]Vcede`
T`_decfTe`ccVbfZcVdRhcZeRS]VaReYWZ]V`cUZcVTe`cj
afS]ZTWf_TeZ`_PPT`_decfTeaReYUVSfX.72=D6R]VceE`.?F==l deRceeZ^Vc
eYZd/deRce.^ZTc`eZ^VECF6, UVeVc^Z_V]`XWZ]VaReY eYZd/aReY.aReY,
TYVT\e`^R\VdfcVaReYViZdedR_UZdhcZeRS]V ZWZdPhcZeRS]VeYZd/aReYl
eYc`h_Vh6iTVaeZ`_=`XTcVReZ`_WRZ]VUf_hcZeRS]VaReY, n
ZWaReYZdUZcVTe`cjfdVeZ^VdeR^aSRdVUWZ]V_R^V ZWZdPUZceYZd/aReYl
eYZd/aReY.eYZd/aReYUReVJ^]`X, n
dVeUVSfXW]RX eYZd/UVSfX.UVSfX,
SnyderSouthwell_5084.book Page 380 Tuesday, July 26, 2005 11:45 AM
C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S 381
dVeR]VceE`RUUcVdd eYZd/R]VceE`.R]VceE`, V_U`WT`_decfTe`c n
cVefc_dacVTZdVeZ^VV]RadVUdZ_TVdeRce afS]ZTWf_TeZ`_V]RadVUl
eZ^V.^ZTc`eZ^VECF6, V]RadVU.eZ^VeYZd/deRce, cVefc_V]RadVU,
n
]`XXVc4]RddaYaT`_eZ_fVd
The class begins by declaring some variables that are necessary for operation, and by defining a constructor method. This constructor notes the exact time of instantiation so that an elapsed time of execution can be generated later, it checks to see if the provided log file path exists and is writable, and it initializes the debug flag (which will determine whether debug-level messages are logged) and registers the email address, if provided, to send alert messages to.
Note that if a directory is provided at the aReY value, the ]`XXVc class generates a date- based filename for the log.
Because the logger captures an execution start time, and because you may need to start logging events right away, the ]`XXVc class should be instantiated as close as possible to the beginning of your script. The public V]RadVU method thus allows you to determine, to the microsecond, how much time has elapsed since the script was called and the ]`XXVc object was constructed.
The constructor method itself does not require any knowledge about the request. But since the logger does need to know these things, the class includes in the next section of the script an RTeZgReV method that collects them:
T`_eZ_fVd]`XXVc4]RddaYa ^RZ_]`XZ_eVcWRTV
acZgReVeZ^V, cVbfVdeeZ^V acZgReVdVddZ`_, dVddZ`_:5 acZgReVRTeZ`_, cVbfVdeVURTeZ`_
acZgReV]`TReZ`_, cVbfVdeVU]`TReZ`_
TR]]RTeZgReVe`UZdT`gVc^VeRUReR
afS]ZTWf_TeZ`_RTeZgReVRTeZ`_.?F==]`TReZ`_.?F==l XVeeZ^VdeR^a
eYZd/eZ^V.UReVJ^U9+Z+deZ^V, XVedVddZ`_:5
eYZd/dVddZ`_.dVddZ`_PZU,
SnyderSouthwell_5084.book Page 381 Tuesday, July 26, 2005 11:45 AM
382 C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S
UZdT`gVcRTeZ`_
ZWV^aejRTeZ`_l eYZd/RTeZ`_.RTeZ`_, n
V]dVl
ZWV^aejP86ELRTeZ`_Nl eYZd/RTeZ`_.P86ELRTeZ`_N, n
V]dVl
eYZd/RTeZ`_.PD6CG6CLC6BF6DEP>6E9@5N, n
n
UZdT`gVc]`TReZ`_
ZWV^aej]`TReZ`_l eYZd/]`TReZ`_.]`TReZ`_, n
V]dVl
eYZd/]`TReZ`_.PD6CG6CLC6BF6DEPFC:N, n
^R\VeYVWZcde]`XV_ecj
eYZd/]`XeYZd/RTeZ`_eYZd/]`TReZ`_, n
]`XXVc4]RddaYaT`_eZ_fVd
When the RTeZgReV method finishes collecting information about the request, it creates a log entry that includes the action and location.
It is important to note that the logged request time is fixed for the duration of the request, so that multiple log entries can be tied together as a group by timestamp. There is a separate mechanism (initialized with eYZd/deRce.^ZTc`eZ^VECF6 in the constructor and accessed using the V]RadVU method) for determining how much time has elapsed since the construction of the ]`XXVc object.
In the next section of the script, the ]`XXVc class continues by defining a private hcZeV method for adding entries to the log buffer, and then using that hcZeV method in the three public logging functions (UVSfX, ]`X, and R]Vce).
T`_eZ_fVd]`XXVc4]RddaYa
SRdZThcZeVWf_TeZ`_W`cfdVhZeYeYcVV]`X]VgV]d acZgReVWf_TeZ`_hcZeV^VddRXVl
hcZeeV_.72=D6,
V_T`UV_Vh]Z_VdZ_^VddRXV
^VddRXV.decPcVa]RTVRccRjM_McRccRjM_Mc^VddRXV,
SnyderSouthwell_5084.book Page 382 Tuesday, July 26, 2005 11:45 AM
C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S 383
TYVT\W`ccVaVReVU^VddRXVacVWZi`_]Rde]Z_V TfccV_e.T`f_eeYZd/SfWWVc,
]Rde.eYZd/SfWWVcLTfccV_e"N, cacVWZi.=Rde]Z_VcVaVReVU, cVaVRed.!,
cdfWWZi.eZ^Vd,
ZWdfSdec]Rde!dec]V_cacVWZi...cacVWZil TYVT\]Rde]Z_VSfe`_VW`cUfa]ZTReV^VddRXV
ZWeYZd/SfWWVcLTfccV_e#N...^VddRXVl
]ZdecVaVRed.Via]`UVdfSdec]Rdedec]V_cacVWZi, cVaVRed,
n n
TYVT\W`cWZcdecVaVRe`W]Rde]Z_V V]dVZW]Rde...^VddRXVl cVaVRed.",
n
ZWcVaVRed..!l RaaV_U_Vh^VddRXV
eYZd/SfWWVcLTfccV_eN.^VddRXV, hcZeeV_.ECF6,
n
V]dVZWcVaVRed.."l RaaV_UUfa]ZTReV^VddRXV
eYZd/SfWWVcLTfccV_eN.cacVWZicVaVRedcdfWWZi, n
V]dVl
cVhcZeVUfa]ZTReV^VddRXV
eYZd/SfWWVcLTfccV_e"N.cacVWZicVaVRedcdfWWZi, n
cVefc_hcZeeV_,
n V_U`WhcZeV^VeY`U ]`XXVc4]RddaYaT`_eZ_fVd
The hcZeV method does the work of determining the content of each log entry. Much of this work is concerned with managing the likely repetition of log entries. When hcZeV is called by one of the logging methods (which follow), it first encodes any linebreaks and/or carriage returns in the message, so that the message will be limited to a single line in the log file. It then checks to see whether the previous message generated a repeat statement. If it did, hcZeV then checks the line previous to that to see what message was repeated. If the current message is yet another repeat, hcZeV determines the number of repeats from the repeat statement, increments it by one, and writes it back to the buffer.
If the previous line is not a repeat statement, hcZeV checks to see whether the current line is itself a repeat of that previous line. If so, a new repeat statement is added to the buffer. If not, the message itself is added to the buffer.
SnyderSouthwell_5084.book Page 383 Tuesday, July 26, 2005 11:45 AM
384 C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S
If a message, rather than a repeat statement, was actually added to the buffer, the hcZeVmethod returns ECF6; otherwise, it returns 72=D6. This information will be used by theR]Vce method.
Now that the basic hcZeV functionality has been created, the three different public logging methods can be defined in the next portion of the script:
T`_eZ_fVd]`XXVc4]RddaYa eYcVV]`X]VgV]d+"UVSfX
afS]ZTWf_TeZ`_UVSfX^VddRXVl ZWeYZd/UVSfXl
eYZd/hcZeV^VddRXV, n
n
eYcVV]`X]VgV]d+#]`X afS]ZTWf_TeZ`_]`X^VddRXVl eYZd/hcZeV^VddRXV, n
eYcVV]`X]VgV]d+$R]Vce afS]ZTWf_TeZ`_R]Vce^VddRXVl
ZW^VddRXVhRdhcZeeV__`ecVaVReVUdV_UR]Vce ZWeYZd/hcZeV^VddRXVl
ZWV^aejeYZd/R]VceE`l
dfS[VTe.2]VceWc`^eYZd/RTeZ`_ReeYZd/]`TReZ`_, dV_e.^RZ]eYZd/R]VceE`dfS[VTe^VddRXV, n
n n
]`XXVc4]RddaYaT`_eZ_fVd
The first logging method, UVSfX, will hcZeV a message to the buffer only if the debugging flag is set. This permits developers to add messages to the log that will aid in development, but then globally turn off all of those extraneous messages when the application is moved into a production environment (where, presumably, all the bugs have been worked out).
The second method, ]`X, is the basic workhorse of the ]`XXVc class; it simply acts as a public wrapper for the private hcZeV method.
The third logging method, R]Vce, goes a step further for critical messages by sending them to the email address that was provided in the constructor. It will send only those messages that are not repeats, that is, where the hcZeV method returned the hcZeeV_ flag with a value of ECF6.
Once the buffer has been filled with log messages over the course of script execution, it needs to be flushed to disk. This is the work of the T`^^Ze method:
SnyderSouthwell_5084.book Page 384 Tuesday, July 26, 2005 11:45 AM
C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S 385
T`_eZ_fVd]`XXVc4]RddaYa
dVcZR]ZkVdSfWWVcR_UhcZeVdZee`WZ]V cVefc_dZ_edZkV`WhcZeeV_]`XZ_SjeVd afS]ZTWf_TeZ`_T`^^Zel
acVWZiVRTY]Z_VhZeYeZ^V dVddZ`_deR^a acVWZi.eYZd/eZ^VeYZd/dVddZ`_,
T`_gVceVRTYV_ecjZ_SfWWVcZ_e`_Vh]Z_V`W]`X W`cVRTYeYZd/SfWWVc2D]Z_Vl
ZWV^aej]Z_VT`_eZ_fV,
`feafe.acVWZi]Z_VMcM_, n
hcZeVe`UZd\
dZkV.WZ]VPafePT`_eV_edeYZd/aReY`feafe7:=6P2AA6?5, cVdVeSfWWVc
eYZd/SfWWVc.RccRj,
W`cUVSfXXZ_XYV]aWf]e`\_`hhYV_]`XhRdT`^^ZeeVU ZWeYZd/UVSfXl
V]RadVU.c`f_UeYZd/V]RadVU%,
eYZd/UVSfX4`^^ZeeVUacVgZ`fdSfWWVcReV]RadVUdVT`_Ud, n
cVefc_dZkV`W`feafe cVefc_dZkV,
n
V_U`W]`XXVcT]Rdd n
0/
The T`^^Ze method takes each of the entries collected in the buffer and writes them into the log file indicated by the value of aReY. As it does so, it prefixes each line with the request time- stamp and the session ID, so that all of the entries from one request can later be grouped together as a coherent unit. Once the entries are written, the buffer is cleared. If the logger is operating in debug mode, a message is added to the new buffer indicating that the previous buffer was flushed to disk.
As we mentioned before, the ]`XXVc class should be instantiated as near as possible to the start of a script, and the T`^^Ze method should be registered as a shutdown function so that it is called without fail at the end of every request, even if the request terminates early due to an ViZe call. To demonstrate this use, we include a simple controller script, shown next. This code can be found also as ]`XXVc4]Rdd5V^`aYa in the Chapter 20 folder of the downloadable archive of code for Pro PHP Security at Yeea+ hhhRacVddT`^.
SnyderSouthwell_5084.book Page 385 Tuesday, July 26, 2005 11:45 AM
386 C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S
-0aYa
fdVdVddZ`_d dVddZ`_PdeRce,
hcZeVUVSfX^VddRXVde`]`X0 UVSfX.ECF6,
]`RU]`XXVcT]Rdd
Z_T]fUVP`_TV]`XXVc4]RddaYa, Z_deR_eZReV]`XXVc
]`X._Vh]`XXVc Y`^V Td_jUVc ]`X UVSfXTd_jUVc1ViR^a]VT`^, cVXZdeVc]`X/T`^^ZeRddYfeU`h_Wf_TeZ`_
cVXZdeVcPdYfeU`h_PWf_TeZ`_RccRj]`XT`^^Ze, dVeRTeZ`_R_U]`TReZ`_
RTeZ`_.eVde,
]`TReZ`_.PD6CG6CLA9APD6=7N, RTeZgReV]`XXZ_X
]`X/RTeZgReVRTeZ`_]`TReZ`_, eYcVVcVaVReVUUVSfX^VddRXVd ]`X/UVSfXEVdeZ_XUVSfX, ]`X/UVSfXEVdeZ_XUVSfX, ]`X/UVSfXEVdeZ_XUVSfX, `_VcVXf]Rc]`X^VddRXV ]`X/]`XEVdeZ_X]`X, `_VR]Vce
]`X/R]VceEVdeZ_XR]Vce, Uf^a]`XXVc`S[VTeW`cUV^`
acZ_e-acV/acZ_ePc]`X"- acV/, ]`XhZ]]SVT`^^ZeeVURedYfeU`h_
0/
This extremely simple demonstration script simply instantiates a new logger object with some appropriate initialization values, registers the logger’s T`^^Ze method as a shutdown func- tion, and then adds some of each flavor of log message. In order to demonstrate the repeated- messages feature of the underlying hcZeV method, the same debug message is added three times. That the class is working can be seen by examining the log’s contents (note that the session ID is truncated for ease of viewing):
SnyderSouthwell_5084.book Page 386 Tuesday, July 26, 2005 11:45 AM
C H A P T E R 2 0 ■ A D D I N G A C C O U N T A B I L I T Y T O T R A C K Y O U R U S E R S 387
#!!&!(""!*+&(+!%RWWLNWW!eVde oTd_jUVc ]`XXVc4]Rdd5V^`aYa
#!!&!(""!*+&(+!%RWWLNWW!EVdeZ_XUVSfX
#!!&!(""!*+&(+!%RWWLNWW!=Rde]Z_VcVaVReVU#eZ^Vd
#!!&!(""!*+&(+!%RWWLNWW!EVdeZ_X]`X
#!!&!(""!*+&(+!%RWWLNWW!EVdeZ_XR]Vce
We see here how each line in the log contains a prefix consisting of time and session informa- tion, followed by the specific information being logged (here merely demonstration content).
Additionally, the R]Vce call sends the following email:
E`+TYd_jUVc1ViR^a]VT`^
DfS[VTe+2]VceWc`^eVdeRe]`XXVc4]Rdd5V^`aYa 5ReV+>`_"";f]#!!&!*+&(+!&!%!!65E 7c`^+_`S`Uj1WZ[Z]`TR]F_acZgZ]VXVUfdVc EVdeZ_XR]Vce
The R]Vce method could, of course, be customized to send additional mail headers so that the email could be automatically sorted into a folder, or to provide more information about the alert (such as the action and location) in order to give an administrator a better idea of why the alert has occurred.