summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cdtproject65
-rw-r--r--.cproject362
-rw-r--r--.gitignore14
-rw-r--r--.gitmodules3
-rw-r--r--.project86
-rw-r--r--AUTHORS11
-rw-r--r--CHANGELOG144
-rw-r--r--Documentation/README13
-rw-r--r--INSTALL21
-rw-r--r--LICENSE6
-rw-r--r--Makefile112
-rw-r--r--Makefile.in122
-rw-r--r--TODO.in18
-rwxr-xr-xautogen.sh17
-rw-r--r--build-apache13.bat47
-rw-r--r--build-apache2.bat62
-rw-r--r--configure.ac127
-rw-r--r--contrib/.cvsignore1
-rw-r--r--contrib/Makefile.in36
-rw-r--r--contrib/README38
-rw-r--r--contrib/create_tables.sql (renamed from create_tables.sql)4
-rw-r--r--[-rwxr-xr-x]contrib/make_combined_log.pl (renamed from make_combined_log.pl)28
-rw-r--r--contrib/mysql_import_combined_log.pl224
-rw-r--r--docs/.cvsignore3
-rw-r--r--docs/Makefile.in60
-rw-r--r--docs/README7
-rw-r--r--docs/documentation.lyx (renamed from Documentation/documentation.lyx)353
-rw-r--r--docs/manual.xml3927
-rw-r--r--include/apache13.h101
-rw-r--r--include/apache20.h25
-rw-r--r--include/mod_log_sql.h166
-rw-r--r--include/winconfig.h78
-rwxr-xr-xinstall-sh251
m---------m40
-rw-r--r--mod_log_sql.c1687
-rw-r--r--src/Makefile.in168
-rw-r--r--src/functions.h261
-rw-r--r--src/functions13.h58
-rw-r--r--src/functions20.h116
-rw-r--r--src/mod_log_sql.c1578
-rw-r--r--src/mod_log_sql_dbd.c132
-rw-r--r--src/mod_log_sql_dbi.c263
-rw-r--r--src/mod_log_sql_logio.c146
-rw-r--r--src/mod_log_sql_mysql.c269
-rw-r--r--src/mod_log_sql_pgsql.c247
-rw-r--r--src/mod_log_sql_ssl.c106
-rw-r--r--utility/Makefile.in49
-rw-r--r--utility/ap_pcre.c344
-rw-r--r--utility/ap_pcre.h178
-rw-r--r--utility/config.c534
-rw-r--r--utility/config.h190
-rw-r--r--utility/database.c212
-rw-r--r--utility/database.h25
-rw-r--r--utility/logparse.c768
-rw-r--r--utility/logparse.h37
-rw-r--r--utility/mod_log_sql.conf70
-rw-r--r--utility/shell.c342
-rw-r--r--utility/shell.h0
-rw-r--r--utility/util.c189
-rw-r--r--utility/util.h26
60 files changed, 12585 insertions, 1942 deletions
diff --git a/.cdtproject b/.cdtproject
new file mode 100644
index 0000000..5491763
--- /dev/null
+++ b/.cdtproject
@@ -0,0 +1,65 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<?eclipse-cdt version="2.0"?>
3
4<cdtproject id="org.eclipse.cdt.make.core.make">
5<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
6<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser">
7<attribute key="addr2line" value="addr2line"/>
8<attribute key="c++filt" value="c++filt"/>
9</extension>
10<extension id="org.eclipse.cdt.core.domsourceindexer" point="org.eclipse.cdt.core.CIndexer"/>
11<data>
12<item id="scannerConfiguration">
13<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"/>
14<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
15<buildOutputProvider>
16<openAction enabled="true" filePath=""/>
17<parser enabled="true"/>
18</buildOutputProvider>
19<scannerInfoProvider id="specsFile">
20<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
21<parser enabled="true"/>
22</scannerInfoProvider>
23</profile>
24<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
25<buildOutputProvider>
26<openAction enabled="false" filePath=""/>
27<parser enabled="true"/>
28</buildOutputProvider>
29<scannerInfoProvider id="makefileGenerator">
30<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/>
31<parser enabled="false"/>
32</scannerInfoProvider>
33</profile>
34<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
35<buildOutputProvider>
36<openAction enabled="false" filePath=""/>
37<parser enabled="true"/>
38</buildOutputProvider>
39<scannerInfoProvider id="specsFile">
40<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
41<parser enabled="false"/>
42</scannerInfoProvider>
43</profile>
44<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
45<buildOutputProvider>
46<openAction enabled="false" filePath=""/>
47<parser enabled="true"/>
48</buildOutputProvider>
49<scannerInfoProvider id="specsFile">
50<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
51<parser enabled="false"/>
52</scannerInfoProvider>
53</profile>
54</item>
55<item id="org.eclipse.cdt.core.pathentry">
56<pathentry include="/home/urkle/Projects/Apache/apache_include/2.0/apr-0" kind="inc" path="" system="true"/>
57<pathentry include="/home/urkle/Projects/Apache/apache_include/2.0/http" kind="inc" path="" system="true"/>
58<pathentry include="/usr/include/mysql" kind="inc" path="" system="true"/>
59<pathentry kind="mac" name="WITH_APACHE20" path="" value=""/>
60<pathentry kind="src" path=""/>
61<pathentry kind="out" path=""/>
62<pathentry kind="con" path="org.eclipse.cdt.make.core.DISCOVERED_SCANNER_INFO"/>
63</item>
64</data>
65</cdtproject>
diff --git a/.cproject b/.cproject
new file mode 100644
index 0000000..ce89a76
--- /dev/null
+++ b/.cproject
@@ -0,0 +1,362 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<?fileVersion 4.0.0?>
3
4<cproject>
5<storageModule moduleId="org.eclipse.cdt.core.settings">
6<cconfiguration id="converted.config.350824298">
7
8
9<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="converted.config.350824298" moduleId="org.eclipse.cdt.core.settings" name="Linux GCC">
10<externalSettings/>
11<extensions>
12<extension id="org.eclipse.cdt.core.domsourceindexer" point="org.eclipse.cdt.core.CIndexer"/>
13<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
14<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
15<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
16<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
17<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
18<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
19</extensions>
20</storageModule>
21<storageModule moduleId="org.eclipse.cdt.core.language.mapping">
22<project-mappings/>
23</storageModule>
24<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
25
26<storageModule moduleId="cdtBuildSystem" version="4.0.0">
27<configuration artifactExtension="" artifactName="mod_log_sql" buildProperties="" description="" errorParsers="" id="converted.config.350824298" name="Linux GCC" parent="org.eclipse.cdt.build.core.prefbase.cfg">
28<folderInfo id="converted.config.350824298." name="/" resourcePath="">
29<toolChain id="cdt.managedbuild.toolchain.gnu.base.1351398655" name="Linux GCC" resourceTypeBasedDiscovery="true" superClass="cdt.managedbuild.toolchain.gnu.base">
30<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF;org.eclipse.cdt.core.GNU_ELF" id="cdt.managedbuild.target.gnu.platform.base.2070600171" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
31<builder appendEnvironment="true" arguments="${project_name} make" autoBuildTarget="all" buildPath="${workspace_loc:/mod_log_sql/Linux GCC}" cleanBuildTarget="clean" command="${HOME}/bin/sshmake" enableAutoBuild="false" enableCleanBuild="true" enabledIncrementalBuild="true" environment="" id="cdt.managedbuild.target.gnu.builder.base.1704124595" incrementalBuildTarget="all" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" stopOnErr="false" superClass="cdt.managedbuild.target.gnu.builder.base"/>
32<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.264929639" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base">
33<option id="gnu.c.compiler.option.include.paths.277599024" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" valueType="includePath">
34<listOptionValue builtIn="false" value="&quot;${workspace_loc:/includes/2.2/apr-1}&quot;"/>
35<listOptionValue builtIn="false" value="&quot;${workspace_loc:/includes/2.2/httpd}&quot;"/>
36<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Includes/mysql}&quot;"/>
37<listOptionValue builtIn="false" value="/usr/include"/>
38</option>
39<option id="gnu.c.compiler.option.preprocessor.def.symbols.1267853547" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" valueType="definedSymbols">
40<listOptionValue builtIn="false" value="WITH_APACHE20=1"/>
41<listOptionValue builtIn="false" value="HAVE_CONFIG_H=1"/>
42</option>
43<option id="gnu.c.compiler.option.preprocessor.nostdinc.444721609" name="Do not search system directories (-nostdinc)" superClass="gnu.c.compiler.option.preprocessor.nostdinc"/>
44<option id="gnu.c.compiler.option.preprocessor.preprocess.1423240087" name="Preprocess only (-E)" superClass="gnu.c.compiler.option.preprocessor.preprocess"/>
45<option id="gnu.c.compiler.option.preprocessor.undef.symbol.732547404" name="Undefined symbols (-U)" superClass="gnu.c.compiler.option.preprocessor.undef.symbol"/>
46<option id="gnu.c.compiler.option.optimization.level.92192040" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level"/>
47<option id="gnu.c.compiler.option.optimization.flags.893054260" name="Other optimization flags" superClass="gnu.c.compiler.option.optimization.flags"/>
48<option id="gnu.c.compiler.option.debugging.level.1989991712" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level"/>
49<option id="gnu.c.compiler.option.debugging.other.1416137166" name="Other debugging flags" superClass="gnu.c.compiler.option.debugging.other"/>
50<option id="gnu.c.compiler.option.debugging.gprof.817771115" name="Generate gprof information (-pg)" superClass="gnu.c.compiler.option.debugging.gprof"/>
51<option id="gnu.c.compiler.option.debugging.prof.1130721011" name="Generate prof information (-p)" superClass="gnu.c.compiler.option.debugging.prof"/>
52<option id="gnu.c.compiler.option.warnings.syntax.1338013879" name="Check syntax only (-fsyntax-only)" superClass="gnu.c.compiler.option.warnings.syntax"/>
53<option id="gnu.c.compiler.option.warnings.pedantic.662901430" name="Pedantic (-pedantic)" superClass="gnu.c.compiler.option.warnings.pedantic"/>
54<option id="gnu.c.compiler.option.warnings.pedantic.error.1267513833" name="Pedantic warnings as errors (-pedantic-errors)" superClass="gnu.c.compiler.option.warnings.pedantic.error"/>
55<option id="gnu.c.compiler.option.warnings.nowarn.805873345" name="Inhibit all warnings (-w)" superClass="gnu.c.compiler.option.warnings.nowarn"/>
56<option id="gnu.c.compiler.option.warnings.allwarn.910485376" name="All warnings (-Wall)" superClass="gnu.c.compiler.option.warnings.allwarn"/>
57<option id="gnu.c.compiler.option.warnings.toerrors.1479340869" name="Warnings as errors (-Werror)" superClass="gnu.c.compiler.option.warnings.toerrors"/>
58<option id="gnu.c.compiler.option.misc.other.1562816435" name="Other flags" superClass="gnu.c.compiler.option.misc.other"/>
59<option id="gnu.c.compiler.option.misc.verbose.1265706406" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose"/>
60<option id="gnu.c.compiler.option.misc.ansi.1838461503" name="Support ANSI programs (-ansi)" superClass="gnu.c.compiler.option.misc.ansi"/>
61<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.616458355" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
62</tool>
63<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1761639329" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base"/>
64<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1017302318" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base"/>
65<tool id="com.redhat.eclipse.cdt.autotools.tool.autogen.131560145" name="autogen.sh" superClass="com.redhat.eclipse.cdt.autotools.tool.autogen"/>
66<tool id="com.redhat.eclipse.cdt.autotools.tool.configure.1351107831" name="configure" superClass="com.redhat.eclipse.cdt.autotools.tool.configure"/>
67<tool id="cdt.managedbuild.tool.gnu.archiver.base.1647937860" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
68<tool id="cdt.managedbuild.tool.gnu.c.linker.base.78392638" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base">
69<option id="gnu.c.link.option.nostart.1696010990" name="Do not use standard start files (-nostartfiles)" superClass="gnu.c.link.option.nostart"/>
70<option id="gnu.c.link.option.nodeflibs.149057311" name="Do not use default libraries (-nodefaultlibs)" superClass="gnu.c.link.option.nodeflibs"/>
71<option id="gnu.c.link.option.nostdlibs.127927094" name="No startup or default libs (-nostdlib)" superClass="gnu.c.link.option.nostdlibs"/>
72<option id="gnu.c.link.option.strip.899862009" name="Omit all symbol information (-s)" superClass="gnu.c.link.option.strip"/>
73<option id="gnu.c.link.option.noshared.2764585" name="No shared libraries (-static)" superClass="gnu.c.link.option.noshared"/>
74<option id="gnu.c.link.option.libs.2040864973" name="Libraries (-l)" superClass="gnu.c.link.option.libs"/>
75<option id="gnu.c.link.option.paths.1741038220" name="Library search path (-L)" superClass="gnu.c.link.option.paths"/>
76<option id="gnu.c.link.option.ldflags.132803838" name="Linker flags" superClass="gnu.c.link.option.ldflags"/>
77<option id="gnu.c.link.option.other.378428644" name="Other options (-Xlinker [option])" superClass="gnu.c.link.option.other"/>
78<option id="gnu.c.link.option.userobjs.495685024" name="Other objects" superClass="gnu.c.link.option.userobjs"/>
79<option id="gnu.c.link.option.shared.626871050" name="Shared (-shared)" superClass="gnu.c.link.option.shared"/>
80<option id="gnu.c.link.option.soname.1808779260" name="Shared object name (-Wl,-soname=)" superClass="gnu.c.link.option.soname"/>
81<option id="gnu.c.link.option.implname.1206228182" name="Import Library name (-Wl,--out-implib=)" superClass="gnu.c.link.option.implname"/>
82<option id="gnu.c.link.option.defname.2102205174" name="DEF file name (-Wl,--output-def=)" superClass="gnu.c.link.option.defname"/>
83<inputType id="cdt.managedbuild.tool.gnu.c.linker.input.2076963094" superClass="cdt.managedbuild.tool.gnu.c.linker.input">
84<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
85<additionalInput kind="additionalinput" paths="$(LIBS)"/>
86</inputType>
87</tool>
88<tool id="cdt.managedbuild.tool.gnu.assembler.base.467752860" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base">
89<option id="gnu.both.asm.option.include.paths.73704182" name="Include paths (-I)" superClass="gnu.both.asm.option.include.paths"/>
90<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1742318494" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
91</tool>
92</toolChain>
93</folderInfo>
94</configuration>
95</storageModule>
96<storageModule moduleId="org.eclipse.cdt.core.pathentry">
97<pathentry kind="src" path=""/>
98<pathentry kind="out" path=""/>
99<pathentry kind="con" path="org.eclipse.cdt.make.core.DISCOVERED_SCANNER_INFO"/>
100<pathentry base-path="includes" include="2.0/apr-0" kind="inc" path="" system="true"/>
101<pathentry base-path="includes" include="2.0/httpd" kind="inc" path="" system="true"/>
102<pathentry kind="mac" name="WITH_APACHE20" path="" value="1"/>
103<pathentry kind="mac" name="HAVE_CONFIG_H" path="" value="1"/>
104</storageModule>
105<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
106
107
108<storageModule moduleId="scannerConfiguration">
109<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"/>
110<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
111<buildOutputProvider>
112<openAction enabled="true" filePath=""/>
113<parser enabled="true"/>
114</buildOutputProvider>
115<scannerInfoProvider id="specsFile">
116<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
117<parser enabled="true"/>
118</scannerInfoProvider>
119</profile>
120<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
121<buildOutputProvider>
122<openAction enabled="false" filePath=""/>
123<parser enabled="true"/>
124</buildOutputProvider>
125<scannerInfoProvider id="makefileGenerator">
126<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/>
127<parser enabled="false"/>
128</scannerInfoProvider>
129</profile>
130<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
131<buildOutputProvider>
132<openAction enabled="false" filePath=""/>
133<parser enabled="true"/>
134</buildOutputProvider>
135<scannerInfoProvider id="specsFile">
136<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
137<parser enabled="false"/>
138</scannerInfoProvider>
139</profile>
140<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
141<buildOutputProvider>
142<openAction enabled="false" filePath=""/>
143<parser enabled="true"/>
144</buildOutputProvider>
145<scannerInfoProvider id="specsFile">
146<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
147<parser enabled="false"/>
148</scannerInfoProvider>
149</profile>
150<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
151<buildOutputProvider>
152<openAction enabled="false" filePath=""/>
153<parser enabled="true"/>
154</buildOutputProvider>
155<scannerInfoProvider id="specsFile">
156<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
157<parser enabled="false"/>
158</scannerInfoProvider>
159</profile>
160<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
161<buildOutputProvider>
162<openAction enabled="false" filePath=""/>
163<parser enabled="true"/>
164</buildOutputProvider>
165<scannerInfoProvider id="specsFile">
166<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
167<parser enabled="false"/>
168</scannerInfoProvider>
169</profile>
170<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
171<buildOutputProvider>
172<openAction enabled="false" filePath=""/>
173<parser enabled="true"/>
174</buildOutputProvider>
175<scannerInfoProvider id="specsFile">
176<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
177<parser enabled="false"/>
178</scannerInfoProvider>
179</profile>
180<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
181<buildOutputProvider>
182<openAction enabled="false" filePath=""/>
183<parser enabled="true"/>
184</buildOutputProvider>
185<scannerInfoProvider id="specsFile">
186<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
187<parser enabled="false"/>
188</scannerInfoProvider>
189</profile>
190<scannerConfigBuildInfo instanceId="converted.config.350824298">
191<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"/>
192<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
193<buildOutputProvider>
194<openAction enabled="true" filePath=""/>
195<parser enabled="true"/>
196</buildOutputProvider>
197<scannerInfoProvider id="specsFile">
198<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
199<parser enabled="true"/>
200</scannerInfoProvider>
201</profile>
202<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
203<buildOutputProvider>
204<openAction enabled="true" filePath=""/>
205<parser enabled="true"/>
206</buildOutputProvider>
207<scannerInfoProvider id="makefileGenerator">
208<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/>
209<parser enabled="true"/>
210</scannerInfoProvider>
211</profile>
212<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
213<buildOutputProvider>
214<openAction enabled="true" filePath=""/>
215<parser enabled="true"/>
216</buildOutputProvider>
217<scannerInfoProvider id="specsFile">
218<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
219<parser enabled="true"/>
220</scannerInfoProvider>
221</profile>
222<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
223<buildOutputProvider>
224<openAction enabled="true" filePath=""/>
225<parser enabled="true"/>
226</buildOutputProvider>
227<scannerInfoProvider id="specsFile">
228<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
229<parser enabled="true"/>
230</scannerInfoProvider>
231</profile>
232<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
233<buildOutputProvider>
234<openAction enabled="true" filePath=""/>
235<parser enabled="true"/>
236</buildOutputProvider>
237<scannerInfoProvider id="specsFile">
238<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
239<parser enabled="true"/>
240</scannerInfoProvider>
241</profile>
242<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
243<buildOutputProvider>
244<openAction enabled="true" filePath=""/>
245<parser enabled="true"/>
246</buildOutputProvider>
247<scannerInfoProvider id="specsFile">
248<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
249<parser enabled="true"/>
250</scannerInfoProvider>
251</profile>
252<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
253<buildOutputProvider>
254<openAction enabled="true" filePath=""/>
255<parser enabled="true"/>
256</buildOutputProvider>
257<scannerInfoProvider id="specsFile">
258<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
259<parser enabled="true"/>
260</scannerInfoProvider>
261</profile>
262<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
263<buildOutputProvider>
264<openAction enabled="true" filePath=""/>
265<parser enabled="true"/>
266</buildOutputProvider>
267<scannerInfoProvider id="specsFile">
268<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
269<parser enabled="true"/>
270</scannerInfoProvider>
271</profile>
272</scannerConfigBuildInfo>
273<scannerConfigBuildInfo instanceId="converted.config.350824298;converted.config.350824298.;cdt.managedbuild.tool.gnu.c.compiler.base.264929639;cdt.managedbuild.tool.gnu.c.compiler.input.616458355">
274<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
275<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
276<buildOutputProvider>
277<openAction enabled="true" filePath=""/>
278<parser enabled="true"/>
279</buildOutputProvider>
280<scannerInfoProvider id="specsFile">
281<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
282<parser enabled="true"/>
283</scannerInfoProvider>
284</profile>
285<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
286<buildOutputProvider>
287<openAction enabled="false" filePath=""/>
288<parser enabled="true"/>
289</buildOutputProvider>
290<scannerInfoProvider id="makefileGenerator">
291<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/>
292<parser enabled="false"/>
293</scannerInfoProvider>
294</profile>
295<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
296<buildOutputProvider>
297<openAction enabled="true" filePath=""/>
298<parser enabled="true"/>
299</buildOutputProvider>
300<scannerInfoProvider id="specsFile">
301<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
302<parser enabled="false"/>
303</scannerInfoProvider>
304</profile>
305<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
306<buildOutputProvider>
307<openAction enabled="true" filePath=""/>
308<parser enabled="true"/>
309</buildOutputProvider>
310<scannerInfoProvider id="specsFile">
311<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
312<parser enabled="false"/>
313</scannerInfoProvider>
314</profile>
315<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
316<buildOutputProvider>
317<openAction enabled="true" filePath=""/>
318<parser enabled="true"/>
319</buildOutputProvider>
320<scannerInfoProvider id="specsFile">
321<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
322<parser enabled="false"/>
323</scannerInfoProvider>
324</profile>
325<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
326<buildOutputProvider>
327<openAction enabled="true" filePath=""/>
328<parser enabled="true"/>
329</buildOutputProvider>
330<scannerInfoProvider id="specsFile">
331<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
332<parser enabled="false"/>
333</scannerInfoProvider>
334</profile>
335<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
336<buildOutputProvider>
337<openAction enabled="true" filePath=""/>
338<parser enabled="true"/>
339</buildOutputProvider>
340<scannerInfoProvider id="specsFile">
341<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
342<parser enabled="false"/>
343</scannerInfoProvider>
344</profile>
345<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
346<buildOutputProvider>
347<openAction enabled="true" filePath=""/>
348<parser enabled="true"/>
349</buildOutputProvider>
350<scannerInfoProvider id="specsFile">
351<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
352<parser enabled="false"/>
353</scannerInfoProvider>
354</profile>
355</scannerConfigBuildInfo>
356</storageModule>
357</cconfiguration>
358</storageModule>
359<storageModule moduleId="cdtBuildSystem" version="4.0.0">
360<project id="mod_log_sql.null.887965423" name="mod_log_sql"/>
361</storageModule>
362</cproject>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..82f7596
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
1/aclocal.m4
2/autom4te.cache
3/config.h*
4/config.nice
5/config.status
6/config.log
7.libs
8Makefile
9/configure
10/stamp-h*
11*.o
12*.lo
13*.slo
14*.la
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..bc62d0d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
1[submodule "m4"]
2 path = m4
3 url = gitosis@git.outoforder.cc:autoconf.git
diff --git a/.project b/.project
new file mode 100644
index 0000000..9af17e9
--- /dev/null
+++ b/.project
@@ -0,0 +1,86 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<projectDescription>
3 <name>mod_log_sql</name>
4 <comment></comment>
5 <projects>
6 <project>includes</project>
7 </projects>
8 <buildSpec>
9 <buildCommand>
10 <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
11 <triggers>clean,full,incremental,</triggers>
12 <arguments>
13 <dictionary>
14 <key>org.eclipse.cdt.make.core.fullBuildTarget</key>
15 <value>all</value>
16 </dictionary>
17 <dictionary>
18 <key>org.eclipse.cdt.make.core.enableAutoBuild</key>
19 <value>false</value>
20 </dictionary>
21 <dictionary>
22 <key>org.eclipse.cdt.make.core.environment</key>
23 <value></value>
24 </dictionary>
25 <dictionary>
26 <key>org.eclipse.cdt.make.core.buildLocation</key>
27 <value>${workspace_loc:/mod_log_sql/Linux GCC}</value>
28 </dictionary>
29 <dictionary>
30 <key>?name?</key>
31 <value></value>
32 </dictionary>
33 <dictionary>
34 <key>org.eclipse.cdt.make.core.enableFullBuild</key>
35 <value>true</value>
36 </dictionary>
37 <dictionary>
38 <key>org.eclipse.cdt.make.core.enableCleanBuild</key>
39 <value>true</value>
40 </dictionary>
41 <dictionary>
42 <key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
43 <value>clean</value>
44 </dictionary>
45 <dictionary>
46 <key>org.eclipse.cdt.make.core.append_environment</key>
47 <value>true</value>
48 </dictionary>
49 <dictionary>
50 <key>org.eclipse.cdt.make.core.contents</key>
51 <value>org.eclipse.cdt.make.core.activeConfigSettings</value>
52 </dictionary>
53 <dictionary>
54 <key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
55 <value>false</value>
56 </dictionary>
57 <dictionary>
58 <key>org.eclipse.cdt.make.core.buildArguments</key>
59 <value>${project_name} make -k</value>
60 </dictionary>
61 <dictionary>
62 <key>org.eclipse.cdt.make.core.buildCommand</key>
63 <value>${HOME}/bin/sshmake</value>
64 </dictionary>
65 <dictionary>
66 <key>org.eclipse.cdt.make.core.autoBuildTarget</key>
67 <value>all</value>
68 </dictionary>
69 <dictionary>
70 <key>org.eclipse.cdt.make.core.stopOnError</key>
71 <value>false</value>
72 </dictionary>
73 </arguments>
74 </buildCommand>
75 <buildCommand>
76 <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
77 <arguments>
78 </arguments>
79 </buildCommand>
80 </buildSpec>
81 <natures>
82 <nature>org.eclipse.cdt.core.cnature</nature>
83 <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
84 <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
85 </natures>
86</projectDescription>
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..d10c5de
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,11 @@
1Edward Rudd <eddie at omegaware dot com>
2 Apache 2.0 port. Current Maintainer as of February, 2004
3
4Christopher B. Powell <chris at grubbybaby dot com>
5 Maintainer since version 1.06
6
7Zeev Suraski <bourbon at netvision.net dot il>
8 Adding the mysql routines (intial revisions)
9
10The Apache Foundation
11 standard apache logging module for which this module is based on
diff --git a/CHANGELOG b/CHANGELOG
index 43624c0..14afb2a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,20 +1,128 @@
1$Id: CHANGELOG,v 1.20 2002/12/18 01:39:28 helios Exp $ 11.101: 2005-02-18?
2 2* fix empty comparison on itemsets for solaris issue #41
3TODO: 3
4* Rethink documentation flow and rewrite? 41.100: 2005-01-11
5* Port connection portion to other DBMS? Genericize the module? Start with 5* fixes for NULL fields (empty request_args)
6 PostgreSQL. 6* fixed a mysql reconnect bug
7* GNU autoconf 7* wrong hostname was logged when using mass virtual hosting
8* merge server config into vh config 8 Use, the new 'V' logformat
9* port to Apache 2.x 9* formatted massvirutalhosting tables to swap - with _.
10* does determining table name in massvirtual mode upon every request 10* included winconfig.h in makefile
11 cause performance degradation? If so fix. 11* fixed quoting of unique_id
12* LogSQLRotateLogs directive with daily/monthly/weekly/etc. 12* converted documentation to OOO Docbook ( a subset of the full docbook DTD)
13* new format char: IP as bigint? 13
14* socket-based middleman daemon with configurable conns, or connect/disconnect. 141.99: 2004-07-28
15* ignore by cookie 15* Added DBI support (not completed yet)
16 16* fixed segfault in the mysql driver escape_string function
17CHANGES: 17* fixed segfault when forcepreserve is set on.
18* switched escape_string driver function to include the string quotes.
19* On Database connection failure Database errors are printed at ERR loglevel
20 instead of debug
21* included build-apache(13|2).bat scripts in the distribution
22
231.98: 2004-05-12
24* re-fixed apache.m4 to better detect APR and APU code
25* fixed for win32 compilation under apache 2
26* lowered minumum required apache 2 version to 2.0.40 (RH9)
27* Added LogSQLDisablePreserve to disable the preserve file.
28* Changed default preserve file location to logs/mod_log_sql-preserve
29 And made LogSQLPreserveFile root relative.
30* fixed bug where preserve file wouldn't be created in apache 2
31* finished TransferLog documentation
32* changed strstr to ap_strstr calls and strchr to ap_strchr calls to
33 stop warning when compiling with AP_DEBUG enabled in apache 2
34* Updated apache m4 to not pass for apache 1.3 if apache 2 is found
35
361.97: 2004-04-08
37* fixed apache.m4 to work with apache 2 setups with different include
38 directories for APR and APU then core Apache
39* cleaned up configuration documentation (updated due to deprecated commands)
40* LogSQLLoginInfo updated to support a connection URI
41* new module "registration" macros to clean up new driver modules
42* mysql driver completely separted from core module
43
441.96: 2004-03-04
45* fixed LogSQLPreserveFile config parameter
46* reworked safe_create_tables and core SQL insert routine.
47* renamed log_sql_* variables and typedefs to logsql_*
48* separated all mysql specific code to separate code module (DB abstraction)
49* separated TODO from Changelog
50* fixed default value of socket file. it's really /var/lib/mysql/mysql.sock not
51 /tmp/mysql.sock
52* Documentation completely converted to Docbook
53* Documentation updated. (still needs more work done)
54* LogSQLDelayedInserts config option removed
55* Added DB generic LogSQLDBParam. Usage is LogSQLDBParam param value
56 example: LogSQLDBParam tcpport 12345
57* notes, cookes, headersin, headersout tables are no longer created unless they
58 are used.
59
601.95: 2004-02-05
61* audit and update of extract_* functions to acheive same output as
62 mod_log_config.
63
641.94: 2004-02-03
65* ssl loggin back in as separate module under 1.3 and 2.0
66* preparsing of format tags is now done to speed up main logging loop.
67* fixed a nasty bug with pointer arithmetic (ick) that caused a segfault with
68 LogSQLWhichCookies
69
701.93: 2004-01-20
71* Compiles for apache 1.3 AND 2.0
72* split apache version specific functions to seperate header files
73* split apache version specific includes to seperate header files
74* added wrapper defines for apache 1.3
75* updated configure m4 scripts to detect both apache versions at the same time
76 and assign defines as to which version was found.
77* made install default to not activate module in configuration files.
78 use make activate instead
79* split SSL logging to separate module (still needs to be finished)
80
811.92: 2004-01-05
82* fixed compilation issue with mysql 4.x where mysql_error returns const char *
83* Have SSL support compiling (though not really tested)
84* updated configure macros to detect mod_ssl.h
85* configure now uses --with-apxs instead of --with-apache
86* Added documentation from 1.19b (needs to be update)
87* Fixed an issue with dependencies in Makefile.in
88
891.91: 2003-12-23
90* Added checks for MySQL to autoconf
91* fixed merge code to work correctly.
92* Merged in 1.19b1 changes
93* Hostnames are now converted to lowercase in the mass-virtual naming section.
94 The loop that converts dots to underscores has been optimized as well.
95* New directive LogSQLTableType allows one to specify the kind of table that
96 the module makes during table creation (e.g. InnoDB, MyISAM). Thanks to Jim
97 Turner for the suggestion and patch. If your MySQL server does not support
98 the specified type, it will create a MyISAM table instead.
99* Directives can now be placed in the 'main' server config and will be
100 inherited by the virtual hosts. This means a LOT less repetition: you only
101 specify the item once to have it inherited, but it can still be overridden
102 on a virtualhost level. These can be specified in this manner:
103
104 LogSQLTransferLogTable
105 LogSQLTransferLogFormat
106 LogSQLPreserveFile
107 LogSQLNotesLogTable
108 LogSQLHeadersInLogTable
109 LogSQLHeadersOutLogTable
110 LogSQLCookieLogTable
111 LogSQLRemhostIgnore
112 LogSQLRequestIgnore
113 LogSQLRequestAccept
114 LogSQLWhichNotes
115 LogSQLWhichHeadersOut
116 LogSQLWhichHeadersIn
117 LogSQLWhichCookies
118 LogSQLWhichCookie
119
1201.90: 2003-12-22
121* updated code to compile under apache 2.0
122* rewrote and consolidate configuration handler routines
123* made all functions static.
124* made delayed insert configurable, instead of compile time
125* moved to my autoconfigure support
18 126
191.18: 1271.18:
20* Delayed inserts (a MySQL extension) are now available at compile-time. 128* Delayed inserts (a MySQL extension) are now available at compile-time.
@@ -351,5 +459,3 @@ to MySQL
351* Segmentation fault in case of certain parameters lacking fixed. 459* Segmentation fault in case of certain parameters lacking fixed.
352* Worked around the SIGPIPE signal that's sent in certain events from 460* Worked around the SIGPIPE signal that's sent in certain events from
353* mysql_query(). Minor modifications 461* mysql_query(). Minor modifications
354
355
diff --git a/Documentation/README b/Documentation/README
deleted file mode 100644
index 349b0ac..0000000
--- a/Documentation/README
+++ /dev/null
@@ -1,13 +0,0 @@
1$Id: README,v 1.2 2002/12/10 20:37:29 helios Exp $
2
3The "original" document is the LyX file "documentation.lyx" -- all other
4files here are derived from it. The most visually accurate document
5and the one I'd recommend reading is the PostScript (.ps) file, although
6a whole host of formats are made available here to suit your preference.
7
8To read the HTML docs, open HTML/documentation.html in your browser.
9To read the .dvi file, use Xdvi or kdvi.
10To read the .ps file, use gv or kghostview.
11To read the .lyx file, use LyX.
12To read the .txt file, use less.
13
diff --git a/INSTALL b/INSTALL
index de45d79..072f738 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,5 +1,20 @@
1$Id: INSTALL,v 1.12 2002/11/14 22:52:54 helios Exp $
2
3This document has been superseded by the new documentation in the 1This document has been superseded by the new documentation in the
4Documentation/ directory. There you will find the docs in a variety of 2docs/ directory. There you will find the docs in a variety of
5formats, including PostScript, plaintext, and HTML. 3formats, including PostScript, plaintext, and HTML.
4
5Basic overview is..
6
7./configure --with-apxs=/path/to/apxs --enable-ssl
8
9gmake
10
11gmake install
12
13
14edit httpd.conf and add the following.
15
16LoadModule log_sql_module modules/mod_log_sql.so
17LoadModule log_sql_mysql_module modules/mod_log_sql_mysql.so
18<IfModule mod_ssl.c>
19LoadModule log_sql_ssl_module modules/mod_log_sql_ssl.so
20</IfModule>
diff --git a/LICENSE b/LICENSE
index 1b82b9d..0c7484d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
1$Id: LICENSE,v 1.4 2002/06/27 20:09:17 helios Exp $
2 1
2Copyright (c) 2004-2006 Edward M. Rudd. All rights reserved.
3Copyright (c) 2002 Christopher B. Powell. All rights reserved. 3Copyright (c) 2002 Christopher B. Powell. All rights reserved.
4Portions copyright (c) 2000 The Apache Software Foundation. 4Portions copyright (c) 2000-2005 The Apache Software Foundation.
5 5
6 6
7Preamble 7Preamble
@@ -117,5 +117,3 @@ WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
117MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 117MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
118 118
119The End 119The End
120
121
diff --git a/Makefile b/Makefile
deleted file mode 100644
index af38739..0000000
--- a/Makefile
+++ /dev/null
@@ -1,112 +0,0 @@
1# $Id: Makefile,v 1.21 2002/12/18 22:56:37 helios Exp $
2
3###########################################################################
4# Important:
5# Adjust these values as outlined in section "Installation" in the docs.
6# Not all are needed at all times.
7
8APACHESOURCE = /usr/local/src/apache_1.3.27-dso
9APACHEINSTALLED = /usr/local/Apache
10APACHEHEADERS = /usr/local/Apache/include
11APXS = $(APACHEINSTALLED)/bin/apxs
12
13MYSQLLIBRARIES = /usr/lib
14MYSQLHEADERS = /usr/include/mysql
15
16#MODSSLHEADERS = /usr/local/src/apache_1.3.27-dso/src/modules/ssl
17#DB1HEADERS = /usr/include/db1
18
19###########################################################################
20# Don't uncomment this without reading the "Optimizing for a busy database"
21# section in the documentation (under "Advanced logging scenarios").
22
23#MYSQLDELAYED = -DWANT_DELAYED_MYSQL_INSERT
24
25###########################################################################
26# Rarely if ever have to touch below here.
27
28MLMVERS = 1.18
29#APXSGDB = -Wc,-g
30APXSOPTS = -Wc,-O2 -Wc,-Wall
31STATOPTS = -fpic -O2 -Wall
32CC = gcc
33INSTALL = /usr/bin/install -m 664
34RM = /bin/rm
35LYX = /usr/bin/lyx
36LATEX = /usr/bin/latex
37DVIPS = /usr/bin/dvips
38LINKS = /usr/bin/lynx
39L2H = /usr/bin/latex2html
40WEBSERV = gw0.corp
41
42STATFLAGS = -I$(APACHEHEADERS)
43SOFLAGS = -L$(MYSQLLIBRARIES) -lmysqlclient -lz
44ifdef MODSSLHEADERS
45 FLAGS = -DEAPI -I$(MYSQLHEADERS) $(MYSQLDELAYED) -I$(MODSSLHEADERS) -I$(DB1HEADERS) -DWANT_SSL_LOGGING
46else
47 FLAGS = -DEAPI -I$(MYSQLHEADERS) $(MYSQLDELAYED)
48endif
49
50all:
51 @echo "You can choose to make mod_log_sql as a static or dynamic module."
52 @echo "Either 'make dso' or 'make static'."
53 @echo
54 @echo "Please first read the documentation carefully!"
55
56dso: mod_log_sql.so
57
58static: mod_log_sql.o
59
60mod_log_sql.so: mod_log_sql.c Makefile
61 $(APXS) -c $(APXSGDB) $(APXSOPTS) $(FLAGS) $(SOFLAGS) mod_log_sql.c
62
63mod_log_sql.o: mod_log_sql.c Makefile
64 $(CC) $(STATOPTS) $(FLAGS) $(STATFLAGS) -c mod_log_sql.c
65
66dsoinstall: dso
67 $(APXS) -i mod_log_sql.so
68
69statinstall: static
70 $(INSTALL) -d -m 755 $(APACHESOURCE)/src/modules/sql
71 $(INSTALL) mod_log_sql.c $(APACHESOURCE)/src/modules/sql/mod_log_sql.c
72 $(INSTALL) Makefile $(APACHESOURCE)/src/modules/sql/Makefile
73 $(INSTALL) mod_log_sql.o $(APACHESOURCE)/src/modules/sql/mod_log_sql.o
74
75clean:
76 $(RM) -rf *.o *.so
77 $(RM) -f Documentation/HTML/*.html Documentation/HTML/*.css Documentation/HTML/*.png
78 $(RM) -f Documentation/*.tex
79 $(RM) -f Documentation/*.dvi
80 $(RM) -f Documentation/*.ps
81 $(RM) -f Documentation/*.txt
82
83distro: documentation
84 @scp CHANGELOG $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/docs
85 @scp Documentation/*.ps $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/docs
86 @scp Documentation/HTML/*.html $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/docs/
87 @scp Documentation/HTML/*.png $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/docs/
88 @cd ..; tar jcf mod_log_sql-$(MLMVERS).tar.bz2 --exclude mod_log_sql/CVS --exclude mod_log_sql/Documentation/CVS --exclude mod_log_sql/Documentation/HTML/CVS --exclude ".directory" mod_log_sql/; scp mod_log_sql-$(MLMVERS).tar.bz2 $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/; rm -f mod_log_sql-$(MLMVERS).tar.bz2
89 @ssh $(WEBSERV) "ln -sf mod_log_sql-$(MLMVERS).tar.bz2 $(APACHEINSTALLED)/html/mod_log_sql/mod_log_sql.tar.bz2"
90
91pre-distro: documentation
92 @cd ..; tar jcf mod_log_sql-$(MLMVERS).tar.bz2 --exclude mod_log_sql/CVS --exclude mod_log_sql/Documentation/CVS --exclude mod_log_sql/Documentation/HTML/CVS --exclude ".directory" mod_log_sql/; scp mod_log_sql-$(MLMVERS).tar.bz2 $(WEBSERV):$(APACHEINSTALLED)/html/mod_log_sql/; rm -f mod_log_sql-$(MLMVERS).tar.bz2
93
94documentation: Documentation/documentation.lyx
95 @echo "Creating LaTeX docs..."
96 @$(LYX) --export latex Documentation/documentation.lyx 2>/dev/null
97 @echo "Creating cross-references...run 1"
98 @cd Documentation ; $(LATEX) documentation.tex >/dev/null 2>&1
99 @echo "Creating cross-references...run 2"
100 @cd Documentation ; $(LATEX) documentation.tex >/dev/null 2>&1
101 @echo "Creating cross-references...run 3"
102 @cd Documentation ; $(LATEX) documentation.tex >/dev/null 2>&1
103 @echo "Creating PostScript docs..."
104 @$(DVIPS) Documentation/documentation.dvi -o Documentation/documentation.ps 2>/dev/null
105 @echo "Creating HTML docs..."
106 @$(L2H) -local_icons -show_section_numbers -split 4 -navigation -noindex_in_navigation -contents_in_navigation -dir Documentation/HTML Documentation/documentation.tex >/dev/null 2>&1
107 @echo "Creating plain text docs..."
108 @$(L2H) -show_section_numbers -split 0 -dir Documentation/ Documentation/documentation.tex >/dev/null 2>&1
109 @$(LINKS) -dump -nolist -width=120 -dump Documentation/documentation.html > Documentation/documentation.txt 2>/dev/null
110 @echo "Cleaning up..."
111 @$(RM) -f Documentation/*.html Documentation/WARNINGS Documentation/*.pl Documentation/*.aux Documentation/*.css Documentation/*.toc Documentation/*.log Documentation/*.old Documentation/*.png Documentation/images.tex
112 @$(RM) -f Documentation/HTML/WARNINGS Documentation/HTML/*.pl Documentation/HTML/*.log Documentation/HTML/*.aux Documentation/HTML/*.tex Documentation/HTML/*.old Documentation/HTML/index.html
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..4c60869
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,122 @@
1# @configure_input@
2
3# Modify these top variables.
4SUBDIRS = docs contrib src utility
5
6EXTRA_DIST = AUTHORS INSTALL TODO LICENSE CHANGELOG \
7 build-apache13.bat build-apache2.bat \
8
9#Don't modify anything below here
10
11top_srcdir = @top_srcdir@
12srcdir = @abs_srcdir@
13builddir = @abs_builddir@
14
15STD_DIST = install-sh \
16 config.sub \
17 config.guess \
18 aclocal.m4 \
19 Makefile.in \
20 configure.ac \
21 configure \
22 stamp-h.in \
23 include/config.h.in
24
25DISTFILES = $(STD_DIST) $(EXTRA_DIST)
26
27all: all-subdirs
28
29all-subdirs install-subdirs activate-subdirs clean-subdirs distclean-subdirs:
30 @otarget=`echo $@|sed s/-subdirs//`; \
31 list=' $(SUBDIRS)'; \
32 for i in $$list; do \
33 if test -d "$$i"; then \
34 target="$$otarget"; \
35 echo "Making $$target in $$i"; \
36 if test "$$i" = "."; then \
37 made_local=yes; \
38 target="local-$$target"; \
39 fi; \
40 (cd $$i && $(MAKE) $$target) || exit 1; \
41 fi; \
42 done;
43
44TODO: TODO.in
45 @./m4/scripts/gen_todo.pl
46
47install: install-subdirs
48 echo "*************************************************************************"; \
49 echo "*** The mod_log_sql modules have been installed."; \
50 echo "*** Please edit your Apache configuration files and"; \
51 echo "*** add the appropriate LoadModule directives per the documentation"; \
52 echo "*** in docs/manual.html"; \
53 echo "*** If you have previously used 1.18 or lower then you must change"; \
54 echo "*** >LoadModule sql_log_module modules/mod_log_sql.so"; \
55 echo "*** to"; \
56 echo "*** >LoadModule log_sql_module modules/mod_log_sql.so"; \
57 echo "*** in your httpd.conf as the internal name of the module has changed."; \
58 echo "*** "; \
59 echo "*** Also read the documentation about using SSL support and new "; \
60 echo "*** configuration directives."; \
61 echo "*************************************************************************";
62
63activate: activate-subdirs
64
65clean: clean-subdirs
66
67distclean: clean distclean-subdirs
68 $(RM) config.status config.log include/config.h include/config.h.in \
69 configure stamp-h stamp-h.in Makefile aclocal.m4
70 $(RM) -r autom4te-2.53.cache
71
72DESTDIR = @PACKAGE_NAME@-@PACKAGE_VERSION@
73DESTTGZ = $(DESTDIR).tar.gz
74dist:
75 @rm -rf $(DESTDIR); \
76 list=' $(SUBDIRS)'; \
77 for i in $$list; do \
78 if test -d "$$i"; then \
79 target=local-dist; \
80 echo "Making $$target in $$i"; \
81 if test "$$i" = "."; then \
82 made_local=yes; \
83 target="local-dist"; \
84 fi; \
85 NEWDESTDIR=$(builddir)/$(DESTDIR)/$$i; \
86 echo $(NEWDESTDIR); \
87 (cd $$i && $(MAKE) DESTDIR=$(builddir)/$(DESTDIR)/$$i $$target) || exit 1; \
88 fi; \
89 done; \
90 if test "$$made_local" != "yes"; then \
91 $(MAKE) "local-dist" || exit 1; \
92 fi; \
93 tar -zcf $(DESTTGZ) $(DESTDIR); \
94 rm -rf $(DESTDIR); \
95
96local-dist: $(DISTFILES)
97 mkdir -p $(DESTDIR)
98 cp -dp --parents $(DISTFILES) $(DESTDIR)
99
100.PHONY: include all-subdirs activate-subdirs install-subdirs \
101 clean-subdirs distclean-subdirs dist
102
103# Regenerate makefiles
104# autoheader might not change config.h.in, so touch a stamp file.
105$(srcdir)/config.h.in: stamp-h.in
106$(srcdir)/stamp-h.in: configure.ac aclocal.m4
107 cd $(srcdir) && autoheader-2.53
108 echo timestamp > $(srcdir)/stamp-h.in
109
110config.h: stamp-h
111
112stamp-h: config.h.in config.status
113 ./config.status
114
115$(srcdir)/configure: configure.ac aclocal.m4
116 cd $(srcdir) && autoconf
117
118Makefile: Makefile.in config.status
119 ./config.status
120
121config.status: configure
122 ./config.status --recheck
diff --git a/TODO.in b/TODO.in
new file mode 100644
index 0000000..bf6b624
--- /dev/null
+++ b/TODO.in
@@ -0,0 +1,18 @@
1TODO:
2* restructure to be more friendly toward DBD pooling
3* validate table names before trying to log them.
4* write alternate DB driver (PostgreSQL, libDBI, mod_*_pool)
5* look at forcing table name to ServerName instead of on of the names in
6 ServerAlias?
7* LogSQLRotateLogs directive with daily/monthly/weekly/etc.
8* socket-based middleman daemon with configurable conns, or connect/disconnect.
9* DBI connection pooling.
10* ignore by cookie
11* investigate thread safety issues
12 Use libmysqlclient_r for threaded MPM (or always?)
13 Add thread locks if using standard mysqlclient
14 Check locking issues with the preserve file
15* rewrite main core logging function to optimize for speed.
16* Clean up table creation code.
17 support DB independent way of defining the tables
18-----
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..869d535
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,17 @@
1#!/bin/sh
2
3if [ -z $ACLOCAL ]; then
4 ACLOCAL=aclocal
5fi
6if [ -z $AUTOCONF ]; then
7 AUTOCONF=autoconf
8fi
9if [ -z $AUTOHEADER ]; then
10 AUTOHEADER=autoheader
11fi
12rm -rf autom4te.cache
13$ACLOCAL -I m4
14$AUTOHEADER
15libtoolize -c --force
16$AUTOCONF
17touch stamp-h.in
diff --git a/build-apache13.bat b/build-apache13.bat
new file mode 100644
index 0000000..77461ee
--- /dev/null
+++ b/build-apache13.bat
@@ -0,0 +1,47 @@
1@echo off
2cls
3rem path to Microsoft SDK installation
4SET DIR_MSSDK=C:\Program Files\Microsoft SDK
5rem path to apache2 installation
6SET DIR_APACHE=C:\Program Files\Apache Group\Apache
7rem path to mysql 4.0 installation
8SET DIR_MYSQL=C:\MySQL
9rem Can be set to opt or debug
10SET LIB_MYSQL=opt
11
12copy /Y winconfig.h config.h
13mkdir Release
14cd Release
15Rem Compile all the source code
16echo /MD /W3 /Zi /Ze /O2 > RESP_c.txt
17echo /DNDEBUG /D_WINDOWS /DWIN32 >> RESP_c.txt
18echo /Fd"mod_log_sql" /FD >> RESP_c.txt
19echo /DHAVE_CONFIG_H /DWITH_APACHE13 /DLOGSQL_DECLARE_EXPORT >> RESP_c.txt
20echo /DSHARED_MODULE >> RESP_c.txt
21echo /I.. >> RESP_c.txt
22echo /I"%DIR_MSSDK%\Include" >> RESP_c.txt
23echo /I"%DIR_APACHE%\Include" >> RESP_c.txt
24echo /I"%DIR_MYSQL%\Include" >> RESP_c.txt
25cl @RESP_c.txt /c ..\mod_log_sql.c ..\mod_log_sql_mysql.c
26
27rem link main module
28echo /MACHINE:I386 /SUBSYSTEM:windows > RESP_l.txt
29echo /OUT:mod_log_sql.so /DLL /OPT:REF /DEBUG >> RESP_l.txt
30echo /LIBPATH:"%DIR_APACHE%\lib" >> RESP_l.txt
31echo /LIBPATH:"%DIR_APACHE%\libexec" >> RESP_l.txt
32echo /LIBPATH:"%DIR_MSSDK%\lib" >> RESP_l.txt
33echo ApacheCore.lib >> RESP_l.txt
34link @RESP_l.txt mod_log_sql.obj
35
36rem link mysql module
37echo /MACHINE:I386 /SUBSYSTEM:windows > RESP_l.txt
38echo /OUT:mod_log_sql_mysql.so /DLL /OPT:REF /DEBUG >> RESP_l.txt
39echo /LIBPATH:"%DIR_APACHE%\lib" >> RESP_l.txt
40echo /LIBPATH:"%DIR_APACHE%\libexec" >> RESP_l.txt
41echo /LIBPATH:"%DIR_MYSQL%\lib\%LIB_MYSQL%" >> RESP_l.txt
42echo /LIBPATH:"%DIR_MSSDK%\lib" >> RESP_l.txt
43echo /NODEFAULTLIB:LIBCMT.lib >> RESP_l.txt
44echo ApacheCore.lib >> RESP_l.txt
45echo libmysql.lib mod_log_sql.lib >> RESP_l.txt
46link @RESP_l.txt mod_log_sql_mysql.obj
47cd ..
diff --git a/build-apache2.bat b/build-apache2.bat
new file mode 100644
index 0000000..c18fa23
--- /dev/null
+++ b/build-apache2.bat
@@ -0,0 +1,62 @@
1@echo off
2rem path to Microsoft SDK installation
3SET DIR_MSSDK=C:\Program Files\Microsoft SDK
4rem path to apache2 installation
5SET DIR_APACHE=C:\Program Files\Apache Group\Apache2
6rem path to mysql 4.0 installation
7SET DIR_MYSQL=C:\MySQL
8rem Can be set to opt or debug
9SET LIB_MYSQL=opt
10rem path to OpenSSL installation
11SET DIR_OPENSSL=C:\OpenSSL
12rem Should be set to VC
13SET LIB_OPENSSL=VC
14
15copy /Y winconfig.h config.h
16mkdir Release
17cd Release
18Rem Compile all the source code
19echo /MD /W3 /Zi /O2 /DNDEBUG /D_WINDOWS /DWIN32 > RESP_c.txt
20echo /Fd"mod_log_sql" /FD >> RESP_c.txt
21echo /DHAVE_CONFIG_H /DWITH_APACHE20 /DLOGSQL_DECLARE_EXPORT >> RESP_c.txt
22echo /I.. >> RESP_c.txt
23echo /I"%DIR_MSSDK%\Include" >> RESP_c.txt
24echo /I"%DIR_APACHE%\Include" >> RESP_c.txt
25echo /I"%DIR_MYSQL%\Include" >> RESP_c.txt
26echo /I"%DIR_OPENSSL%\Include" >> RESP_c.txt
27echo /I"%DIR_OPENSSL%\Include\openssl" >> RESP_c.txt
28cl @RESP_c.txt /c ..\mod_log_sql.c ..\mod_log_sql_mysql.c
29
30if not exist "%DIR_APACHE%\Include\mod_ssl.h" goto nossl
31cl @RESP_C.txt /c ..\mod_log_sql_ssl.c
32:nossl
33
34rem link main module
35echo /MACHINE:I386 /SUBSYSTEM:windows > RESP_l.txt
36echo /OUT:mod_log_sql.so /DLL /OPT:REF /DEBUG >> RESP_l.txt
37echo /LIBPATH:"%DIR_APACHE%\lib" >> RESP_l.txt
38echo libapr.lib libaprutil.lib libhttpd.lib >> RESP_l.txt
39link @RESP_l.txt mod_log_sql.obj
40
41rem link mysql module
42echo /MACHINE:I386 /SUBSYSTEM:windows > RESP_l.txt
43echo /OUT:mod_log_sql_mysql.so /DLL /OPT:REF /DEBUG >> RESP_l.txt
44echo /LIBPATH:"%DIR_APACHE%\lib" >> RESP_l.txt
45echo /LIBPATH:"%DIR_MYSQL%\lib\%LIB_MYSQL%" >> RESP_l.txt
46echo /NODEFAULTLIB:LIBCMT.lib >> RESP_l.txt
47echo libapr.lib libaprutil.lib libhttpd.lib >> RESP_l.txt
48echo libmysql.lib mod_log_sql.lib >> RESP_l.txt
49link @RESP_l.txt mod_log_sql_mysql.obj
50
51if not exist "%DIR_APACHE%\Include\mod_ssl.h" goto done
52rem link ssl module
53echo /MACHINE:I386 /SUBSYSTEM:windows > RESP_l.txt
54echo /OUT:mod_log_sql_ssl.so /DLL /OPT:REF /DEBUG >> RESP_l.txt
55echo /LIBPATH:"%DIR_APACHE%\lib" >> RESP_l.txt
56echo /LIBPATH:"%DIR_OPENSSL%\lib\%LIB_OPENSSL%" >> RESP_l.txt
57echo libapr.lib libaprutil.lib libhttpd.lib >> RESP_l.txt
58echo mod_log_sql.lib >> RESP_l.txt
59link @RESP_l.txt mod_log_sql_ssl.obj
60
61:done
62cd ..
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..395398f
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,127 @@
1dnl Required initializer
2AC_INIT([mod_log_sql],[1.102])
3OOO_CONFIG_NICE(config.nice)
4AC_PREREQ(2.59)
5AC_CONFIG_HEADERS(include/autoconfig.h)
6AC_CONFIG_SRCDIR(src/mod_log_sql.c)
7
8OOO_MAINTAIN_MODE
9
10dnl Add a test for a compiler.
11AC_PROG_CC
12AC_PROG_LIBTOOL
13
14PKG_CHECK_MODULES(PCRE,libpcre)
15
16APACHE20_VERSION=2.0.40
17APACHE13_VERSION=1.3.20
18CHECK_APACHE($APACHE13_VERSION,$APACHE20_VERSION,
19 :,
20 :,
21 AC_MSG_ERROR([*** The correct version Apache was not found!])
22 AC_MSG_ERROR([*** You need either Apache 1.3 version $APACHE13_VERSION or greater])
23 AC_MSG_ERROR([*** or Apache 2.0/2.2 version $APACHE20_VERSION or greater!])
24 )
25
26if test $AP_VERSION = "2.0"; then
27 WANT_LOGIO_MOD=1
28else
29 WANT_LOGIO_MOD=0
30fi
31
32AC_SUBST(WANT_LOGIO_MOD)
33
34CHECK_APU_HEADERS([apr_dbd.h],
35 WANT_DBD_MOD=1,
36 WANT_DBD_MOD=0)
37
38AC_SUBST(WANT_DBD_MOD)
39CHECK_APU_FUNCS(apr_dbd_transaction_mode_get)
40
41CHECK_MYSQL(
42 WANT_MYSQL_MOD=1,
43 AC_MSG_WARN([*** Mysql client libraries not found!])
44 WANT_MYSQL_MOD=0
45 )
46
47AC_SUBST(WANT_MYSQL_MOD)
48
49CHECK_LIBDBI(WANT_DBI_MOD=1,
50 AC_MSG_WARN([** libDBI client libraries not found!])
51 WANT_DBI_MOD=0
52 )
53AC_SUBST(WANT_DBI_MOD)
54
55dnl to write, checking for pgsql libs
56WANT_PGSQL_MOD=0
57AC_SUBST(WANT_PGSQL_MOD)
58
59
60CHECK_MOD_SSL(
61 WANT_SSL_MOD=1,
62 AC_MSG_WARN([** mod_ssl.h not found or missing SSL headers!])
63 WANT_SSL_MOD=0
64 )
65
66AC_SUBST(WANT_SSL_MOD)
67
68case "$target" in
69 *-*-solaris* | *-*-osf* )
70 if test $AP_VERSION = "1.3"; then
71 RT_LIBS=-lrt
72 fi
73 ;;
74 *)
75 RT_LIBS=""
76 ;;
77esac
78
79AC_SUBST(RT_LIBS)
80
81
82AC_CHECK_HEADERS(limits.h)
83
84AC_CONFIG_FILES([stamp-h], [echo timestamp > stamp-h])
85
86dnl Write config.status and the Makefile
87
88AC_CONFIG_FILES([Makefile
89src/Makefile
90docs/Makefile
91utility/Makefile
92contrib/Makefile])
93AC_OUTPUT
94
95
96AC_MSG_RESULT([------------------------------------])
97AC_MSG_RESULT([Apache version : $AP_VERSION])
98if test $WANT_SSL_MOD -eq 1; then
99 AC_MSG_RESULT([SSL Support : yes])
100else
101 AC_MSG_RESULT([SSL Support : no])
102 AC_MSG_RESULT([*** Make sure OpenSSL headers, and mod_ssl.h are installed.])
103fi
104if test $WANT_LOGIO_MOD -eq 1; then
105 AC_MSG_RESULT([LogIO Module : yes])
106else
107 AC_MSG_RESULT([LogIO Module : no])
108 AC_MSG_RESULT([*** Logio does not work with Apache 1.3.])
109fi
110
111AC_MSG_RESULT([Enabled drivers :])
112if test $WANT_MYSQL_MOD -eq 1; then
113 AC_MSG_RESULT([ MySQL Driver])
114fi
115if test $WANT_PGSQL_MOD -eq 1; then
116 AC_MSG_RESULT([ PostgreSQL Driver])
117fi
118if test $WANT_DBI_MOD -eq 1; then
119 AC_MSG_RESULT([ libDBI Driver])
120fi
121if test $WANT_DBD_MOD -eq 1; then
122 AC_MSG_RESULT([ APR DBD Driver])
123fi
124if test $OOO_MAINTAIN -eq 1; then
125 AC_MSG_RESULT([Maintainer mode is on. -Werror is in effect])
126fi
127AC_MSG_RESULT([------------------------------------])
diff --git a/contrib/.cvsignore b/contrib/.cvsignore
new file mode 100644
index 0000000..f3c7a7c
--- /dev/null
+++ b/contrib/.cvsignore
@@ -0,0 +1 @@
Makefile
diff --git a/contrib/Makefile.in b/contrib/Makefile.in
new file mode 100644
index 0000000..c2bd203
--- /dev/null
+++ b/contrib/Makefile.in
@@ -0,0 +1,36 @@
1# @configure_input@
2
3# Modify these top variables.
4SUBDIRS =
5
6EXTRA_DIST = README \
7 create_tables.sql \
8 make_combined_log.pl \
9 mysql_import_combined_log.pl
10
11#Don't modify anything below here
12
13srcdir = @abs_srcdir@
14builddir = @abs_builddir@
15
16STD_DIST = Makefile.in
17
18DISTFILES = $(STD_DIST) $(EXTRA_DIST)
19
20all:
21
22install:
23
24update:
25
26clean:
27
28distclean: clean
29 $(RM) Makefile
30
31local-dist: $(DISTFILES)
32 mkdir -p $(DESTDIR)
33 cp -dp --parents $(DISTFILES) $(DESTDIR)
34
35.PHONY: include all-subdirs update-subdirs install-subdirs \
36 clean-subdirs distclean-subdirs dist
diff --git a/contrib/README b/contrib/README
new file mode 100644
index 0000000..bf0e65f
--- /dev/null
+++ b/contrib/README
@@ -0,0 +1,38 @@
1This directory contains contributed scripts/programs/utilites for mod_log_sql.
2
3* create_tables.sql
4
5This is the create table SQL commands to create the access, headers_in,
6headers_out, cookies, and notes tables in the MySQL database.
7Use it like this.
8mysql -u user -h host -p apachelogdatabase < create_tables.sql
9Where:
10 user is the username to log in as,
11 host is the hostname the server is on,
12 apachelogdatabase is the database to put the tables into
13 -p will have mysql ask you for a password for the user
14
15*make_combined_log.pl
16
17This perl script will extract the data from mod_log_sql's tables in the
18database and export a standard Apache combined log file. Use this to run
19logs through a program like webalizer.
20
21You must edit the perl script to configure variables before you run it.
22Usage:
23./make_combined_log.pl days virtualhost
24Where:
25 days is the number of days to fetch (starting from now and going back
26 in time)
27 virtualhost is the name of the virtualhost to retrieve
28
29Example:
30 ./make_combined_log.pl 2 example.com
31
32*mysql_import_combined_log.pl
33
34This is a perl script written by Aaron Jenson that imports a combined log file
35from apache into a SQL database table.. You can use this script to import logs
36from a webserver you are converting over from the standard Apache log system to
37mod_log_sql. A Usage statement can be fetch by running the program with no
38parameters or with --help or -?.
diff --git a/create_tables.sql b/contrib/create_tables.sql
index dbcfbe4..0fe0f4b 100644
--- a/create_tables.sql
+++ b/contrib/create_tables.sql
@@ -15,7 +15,7 @@ create table access_log (
15 request_method varchar(10) , 15 request_method varchar(10) ,
16 request_protocol varchar(10) , 16 request_protocol varchar(10) ,
17 request_time char(28), 17 request_time char(28),
18 request_uri varchar(50), 18 request_uri varchar(255),
19 request_args varchar(255), 19 request_args varchar(255),
20 server_port smallint unsigned, 20 server_port smallint unsigned,
21 ssl_cipher varchar(25), 21 ssl_cipher varchar(25),
@@ -23,7 +23,7 @@ create table access_log (
23 ssl_maxkeysize smallint unsigned, 23 ssl_maxkeysize smallint unsigned,
24 status smallint unsigned , 24 status smallint unsigned ,
25 time_stamp int unsigned , 25 time_stamp int unsigned ,
26 virtual_host varchar(50) 26 virtual_host varchar(255)
27); 27);
28 28
29create table notes ( 29create table notes (
diff --git a/make_combined_log.pl b/contrib/make_combined_log.pl
index 1741dbf..2c55271 100755..100644
--- a/make_combined_log.pl
+++ b/contrib/make_combined_log.pl
@@ -1,6 +1,6 @@
1#!/usr/bin/perl 1#!/usr/bin/perl
2 2
3# $Id: make_combined_log.pl,v 1.5 2002/11/14 03:51:34 helios Exp $ 3# $Id: make_combined_log.pl,v 1.2 2004/02/12 23:32:55 urkle Exp $
4# 4#
5# make_combined_log.pl 5# make_combined_log.pl
6# 6#
@@ -34,14 +34,9 @@ $| = 1;
34 34
35use DBI; 35use DBI;
36 36
37# Remember, $#ARGV is parameters minus one... 37# Remove the # in front of this line when you have
38if ($#ARGV != 1) { 38# edited the variables below.
39 die "Incorrect usage, please read the perl source code for correct usage." 39#$has_edited_source = 1;
40}
41
42$days = $ARGV[0];
43$virthost = $ARGV[1];
44
45# 40#
46# Set up the proper variables to permit database access 41# Set up the proper variables to permit database access
47# 42#
@@ -52,6 +47,20 @@ $serverPass = "somepass";
52$serverTbl = "acc_log_tbl"; 47$serverTbl = "acc_log_tbl";
53$serverDb = "apache"; 48$serverDb = "apache";
54 49
50if (!defined($has_edited_source)) {
51 print "Please edit this file and configure it first.\n";
52 print "This program is $0\n";
53 exit 1;
54}
55# Remember, $#ARGV is parameters minus one...
56if ($#ARGV != 1) {
57 print "Usage $0 days virtualhost\n";
58 exit 1;
59}
60
61$days = $ARGV[0];
62$virthost = $ARGV[1];
63
55# 64#
56# Other constants 65# Other constants
57# 66#
@@ -128,4 +137,3 @@ while (@data = $records->fetchrow_array) {
128# Done 137# Done
129# 138#
130$records->finish; 139$records->finish;
131
diff --git a/contrib/mysql_import_combined_log.pl b/contrib/mysql_import_combined_log.pl
new file mode 100644
index 0000000..bffec20
--- /dev/null
+++ b/contrib/mysql_import_combined_log.pl
@@ -0,0 +1,224 @@
1#!/usr/bin/perl -w
2# $Id: mysql_import_combined_log.pl,v 1.4 2004/02/21 18:09:50 urkle Exp $
3# Written by Aaron Jenson.
4# Original source: http://www.visualprose.com/software.php
5# Updated to work under Perl 5.6.1 by Edward Rudd
6use strict;
7use Getopt::Long qw(:config bundling);
8use DBI;
9use Date::Parse;
10
11my %options = ();
12my $i = 0;
13my $sql = '';
14my $valuesSql = '';
15my $line = '';
16my $dbh = 0;
17my $sth = 0;
18my @parts = ();
19my $part;
20my $TIMESTAMP = 3;
21my $REQUEST_LINE = 4;
22my @cols = (
23 'remote_host', ## 0
24 'remote_logname', ## 1
25 'remote_user', ## 2
26 'request_time', ## 3.string
27 'time_stamp', ## 3.posix
28 'request_line', ## 5
29 'request_method', ## 6
30 'request_uri', ## 7
31 'request_args', ## 8
32 'request_protocol', ## 9
33 'status', ## 10
34 'bytes_sent', ## 11
35 'referer', ## 12
36 'agent' ## 13
37);
38my $col = '';
39
40GetOptions (\%options,
41 "version" => sub { VERSION_MESSAGE(); exit 0; },
42 "help|?" => sub { HELP_MESSAGE(); exit 0; },
43 "host|h=s",
44 "database|d=s",
45 "table|t=s",
46 "username|u=s",
47 "password|p=s",
48 "logfile|f=s");
49
50$options{host} ||= 'localhost';
51$options{database} ||= '';
52$options{username} ||= '';
53$options{password} ||= '';
54$options{logfile} ||= '';
55
56if( ! $options{database} )
57{
58 HELP_MESSAGE();
59 print "Must supply a database to connect to.\n";
60 exit 1;
61}
62
63if( ! $options{table} )
64{
65 HELP_MESSAGE();
66 print "Must supply table name.\n";
67 exit 1;
68}
69
70if( $options{logfile} )
71{
72 if( ! -e $options{logfile} )
73 {
74 print "File '$options{logfile}' doesn't exist.\n";
75 exit 1;
76 }
77 open(STDIN, "<$options{logfile}") || die "Can't open $options{logfile} for reading.";
78}
79
80$dbh = Connect();
81if (! $dbh) {
82 exit 1;
83}
84
85$sql = "INSERT INTO $options{table} (";
86foreach $col (@cols)
87{
88 $sql .= "$col," if( $col );
89}
90chop($sql);
91$sql .= ') VALUES (';
92my ($linecount,$insertcount) = (0,0);
93while($line = <STDIN>)
94{
95 $linecount++;
96 @parts = SplitLogLine( $line );
97 next if( $parts[$TIMESTAMP+1] == 0 );
98 $valuesSql = '';
99 for( $i = 0; $i < @cols; ++$i )
100 {
101 $parts[$i] =~ s/\\/\\\\/g;
102 $parts[$i] =~ s/'/\\'/g;
103 $valuesSql .= "'$parts[$i]'," if( $cols[$i] );
104 }
105 chop($valuesSql);
106
107 $sth = $dbh->prepare("$sql$valuesSql)");
108 if( ! $sth->execute() )
109 {
110 print "Unable to perform specified query.\n$sql$valuesSql\n" . $sth->errstr() . "\n";
111 } else {
112 $insertcount++;
113 }
114 $sth->finish();
115}
116print "Parsed $linecount Log lines\n";
117print "Inserted $insertcount records\n";
118print "to table '$options{table}' in database '$options{database}' on '$options{host}'\n";
119
120# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
121# Connects to a MySQL database and returns the connection.
122# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
123sub Connect
124{
125 my $dsn = "DBI:mysql:$options{database};hostname=$options{host}";
126 return DBI->connect( $dsn, $options{username}, $options{password} );
127}
128
129
130# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
131# Splits up a log line into its parts.
132# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
133sub SplitLogLine
134{
135 my $line = shift;
136 my $i = 0;
137 my $inQuote = 0;
138 my $char = '';
139 my $part = '';
140 my @parts = ();
141 my $count = 0;
142 chomp($line);
143 for( $i = 0; $i < length($line); ++$i )
144 {
145 $char = substr($line, $i, 1);
146 if( $char eq ' ' && ! $inQuote )
147 {
148 ## print "Found part $part.\n";
149 if( $count == $TIMESTAMP )
150 {
151 push(@parts, "[".$part."]");
152 $part = str2time($part);
153 }
154 push(@parts, $part);
155 if( $count == $REQUEST_LINE )
156 {
157 my @request = split(/[ ?]/, $part);
158 push(@parts, $request[0]);
159 push(@parts, $request[1]);
160 if( $request[3] )
161 {
162 push(@parts, $request[2]);
163 push(@parts, $request[3]);
164 }
165 else
166 {
167 push(@parts, '');
168 push(@parts, $request[2]);
169 }
170 $count += 5;
171 }
172 else
173 {
174 ++$count;
175 }
176 $part = '';
177 }
178 elsif( $char eq '"' || $char eq '[' || $char eq ']' )
179 {
180 $inQuote = !$inQuote;
181 }
182 else
183 {
184 $part .= $char;
185 }
186 }
187 push(@parts,$part) if $part;
188
189 return @parts;
190}
191
192
193# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
194# Prints the usage/help message for this program.
195# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
196sub HELP_MESSAGE
197{
198 print<<EOF;
199Imports an Apache combined log into a MySQL database.
200Usage: mysql_import_combined_log.pl -d <database name> -t <table name> [-h <hostname>] [-u <username>] [-p <password>] [-f <filename]
201 --host|-h <host name> The host to connect to. Default is localhost.
202 --database|-d <database name> The database to use. Required.
203 --username|-u <username> The user to connect as.
204 --password|-p <password> The user's password.
205 --table|-t <table name> The name of the table in which to insert data.
206 --logfile|-f <file name> The file to read from. If not given, data is read from stdin.
207 --help|-? Print out this help message.
208 --version Print out the version of this software.
209EOF
210}
211
212
213
214# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
215# Prints the version information for this program
216# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
217sub VERSION_MESSAGE
218{
219 print "mysql_import_combined_log.pl version 1.2\n";
220 print "Version 1.0 Written by Aaron Jenson.\n";
221 print "Update to work with perl 5.6.1 by Edward Rudd\n";
222}
223
2241;
diff --git a/docs/.cvsignore b/docs/.cvsignore
new file mode 100644
index 0000000..73cd04a
--- /dev/null
+++ b/docs/.cvsignore
@@ -0,0 +1,3 @@
1Makefile
2manual.html
3manual.pdf
diff --git a/docs/Makefile.in b/docs/Makefile.in
new file mode 100644
index 0000000..d88e72b
--- /dev/null
+++ b/docs/Makefile.in
@@ -0,0 +1,60 @@
1# @configure_input@
2
3# Modify these top variables.
4SUBDIRS =
5
6EXTRA_DIST = README \
7 manual.xml \
8 manual.html
9
10#Don't modify anything below here
11
12srcdir = @abs_srcdir@
13builddir = @abs_builddir@
14
15STD_DIST = Makefile.in
16
17DISTFILES = $(STD_DIST) $(EXTRA_DIST)
18
19all: all-subdirs
20
21%.html: %.xml
22 @xmlto xhtml-nochunks $<
23
24%.pdf: %.xml
25 @xmlto pdf $<
26
27all-subdirs install-subdirs update-subdirs clean-subdirs distclean-subdirs:
28 @otarget=`echo $@|sed s/-subdirs//`; \
29 list=' $(SUBDIRS)'; \
30 for i in $$list; do \
31 if test -d "$$i"; then \
32 target="$$otarget"; \
33 echo "Making $$target in $$i"; \
34 if test "$$i" = "."; then \
35 made_local=yes; \
36 target="local-$$target"; \
37 fi; \
38 (cd $$i && $(MAKE) $$target) || exit 1; \
39 fi; \
40 done; \
41
42include:
43 rm -rf include
44 ln -s @APACHE_INCDIR@ include
45
46install: install-subdirs
47
48update: update-subdirs
49
50clean: clean-subdirs
51
52distclean: clean distclean-subdirs
53 $(RM) Makefile
54
55local-dist: $(DISTFILES)
56 mkdir -p $(DESTDIR)
57 cp -dp --parents $(DISTFILES) $(DESTDIR)
58
59.PHONY: include all-subdirs update-subdirs install-subdirs \
60 clean-subdirs distclean-subdirs dist
diff --git a/docs/README b/docs/README
new file mode 100644
index 0000000..dd40351
--- /dev/null
+++ b/docs/README
@@ -0,0 +1,7 @@
1The "original" document is the Docbook file "manual.xml" -- all other
2files here are derived from it.
3
4To read the HTML docs, open manual.html in your browser.
5
6To generate other formats of the documentation use xmlto or your favorite
7xslt docbook converter to convert the xml file.
diff --git a/Documentation/documentation.lyx b/docs/documentation.lyx
index a678315..eeb6af5 100644
--- a/Documentation/documentation.lyx
+++ b/docs/documentation.lyx
@@ -1,5 +1,5 @@
1#LyX 1.2 created this file. For more info see http://www.lyx.org/ 1#LyX 1.3 created this file. For more info see http://www.lyx.org/
2\lyxformat 220 2\lyxformat 221
3\textclass article 3\textclass article
4\language english 4\language english
5\inputencoding default 5\inputencoding default
@@ -162,7 +162,7 @@ Note which directives go in the 'main server config' and which directives
162 This is made clear in the directive documentation. 162 This is made clear in the directive documentation.
163\layout Itemize 163\layout Itemize
164 164
165The 'time_stamp' field is stored in an UNSIGNED INTEGER column, in the standard 165The 'time_stamp' field is stored in an UNSIGNED INTEGER format, in the standard
166 unix 166 unix
167\begin_inset Quotes eld 167\begin_inset Quotes eld
168\end_inset 168\end_inset
@@ -173,9 +173,9 @@ seconds since the epoch
173 173
174 format. 174 format.
175 This is superior to storing the access time as a string due to size requirement 175 This is superior to storing the access time as a string due to size requirement
176s: an UNSIGNED INT requires 4 bytes, whereas an Apache date string -- e.g. 176s: an UNSIGNED INT requires 4 bytes, whereas an Apache date string (e.g.
177 "18/Nov/2001:13:59:52 -0800" -- requires 26 bytes: those extra 22 bytes 177 "18/Nov/2001:13:59:52 -0800") requires 26 bytes: those extra 22 bytes become
178 become significant when multiplied by thousands of accesses on a busy server. 178 significant when multiplied by thousands of accesses on a busy server.
179 Besides, an INT type is far more flexible for comparisons, etc. 179 Besides, an INT type is far more flexible for comparisons, etc.
180\begin_deeper 180\begin_deeper
181\layout Standard 181\layout Standard
@@ -301,6 +301,11 @@ These installation documents assume a relatively modern GNU/Linux scenario.
301 compiling the module for those platforms. 301 compiling the module for those platforms.
302\layout Subsubsection 302\layout Subsubsection
303 303
304
305\begin_inset LatexCommand \label{sub:Solaris}
306
307\end_inset
308
304Solaris 309Solaris
305\layout Standard 310\layout Standard
306 311
@@ -387,6 +392,143 @@ contact
387\end_inset 392\end_inset
388 393
389 and help fill in this section. 394 and help fill in this section.
395\layout Subsubsection
396
397OS X
398\layout Standard
399
400mod_log_sql should compile and work out-of-the-box on this platform.
401 Here are some notes from a user successfully running the module on OS X:
402\layout Quote
403
404
405\emph on
406The only changes I had to make were to where I had the various libraries
407 installed.
408\layout Quote
409
410
411\emph on
412Here are the changes I made to the head of the Makefile:
413\layout LyX-Code
414
415APACHESOURCE = /usr/local/src/apache_1.3.27
416\layout Quote
417
418
419\emph on
420(Wasn't sure if this was really needed or not, so I downloaded the Apache
421 source just in case)
422\layout LyX-Code
423
424APACHEINSTALLED = /usr/sbin
425\layout LyX-Code
426
427APACHEHEADERS = /usr/include/httpd
428\layout LyX-Code
429
430APXS = $(APACHEINSTALLED)/apxs
431\layout LyX-Code
432
433MYSQLLIBRARIES = /usr/local/mysql/lib
434\layout LyX-Code
435
436MYSQLHEADERS = /usr/local/mysql/include
437\layout Quote
438
439
440\emph on
441I'm using a binary installation of MySQL and the default apache installation
442 on OS X Client 10.2.3, the locations of these files may vary depending on
443 how you've installed MySQL and will almost certainly be different if you're
444 using OS X Server.
445\layout Standard
446
447My thanks to Tom Wiebe for being the first (to my knowlege) mod_log_sql
448 user on OS X and for providing these notes.
449\layout Subsubsection
450
451Digital Unix
452\layout Standard
453
454Digital Unix, like Solaris, needs to be linked against librt; see section
455
456\begin_inset LatexCommand \ref{sub:Solaris}
457
458\end_inset
459
460.
461 Here are further notes from a user successfully running the module on Digital
462 Unix:
463\layout Quote
464
465
466\emph on
467Instead of trying to get the module to remember where the MySQL libraries
468 were, I instead compiled apache with the information:
469\layout Quote
470
471
472\emph on
473LDFLAGS='-rpath /isp/mysql/lib/mysql' ./configure ...
474\layout Quote
475
476
477\emph on
478Everything worked as expected after that.
479 (The error I got without this was "/sbin/loader: Fatal Error: cannot map
480 libmysqlclient.so" )
481\layout Quote
482
483
484\emph on
485Digital Unix (v4.0f, at least ) appears to follow the same requirements needed
486 by Solaris, so simply adding librt to the module made it compile without
487 errors.
488\layout Quote
489
490
491\emph on
492As for the warnings, here's the text:
493\layout LyX-Code
494
495
496\emph on
497mod_log_sql.c: In function `extract_request_duration':
498\layout LyX-Code
499
500
501\emph on
502mod_log_sql.c:292: warning: long int format, different type arg (arg 4)
503\layout LyX-Code
504
505
506\emph on
507mod_log_sql.c: In function `extract_request_timestamp':
508\layout LyX-Code
509
510
511\emph on
512mod_log_sql.c:497: warning: long int format, different type arg (arg 4)
513\layout Quote
514
515
516\emph on
517Poking around in the code, it looks like the compiler was complaining that
518 what time() is returning doesn't play nicely with %ld by default.
519 I just typecast them as (long)'s and the warnings went away ( not that
520 the module wasn't working correctly without them ).
521\layout Quote
522
523
524\emph on
525The module works very well so far in testing...
526 hasn't dropped a single log entry yet.
527
528\layout Standard
529
530My thanks to Jim Turner for permitting me to quote him here, and for being
531 the first known user of mod_log_sql on Digital Unix.
390\layout Subsection 532\layout Subsection
391 533
392Do I want a DSO or a static module? 534Do I want a DSO or a static module?
@@ -919,10 +1061,24 @@ mysql> create database apachelogs;
919\end_inset 1061\end_inset
920 1062
921If you want to hand-create the tables, run the enclosed 'create-tables' 1063If you want to hand-create the tables, run the enclosed 'create-tables'
922 SQL script as follows: 1064 SQL script as follows (
1065\begin_inset Quotes eld
1066\end_inset
1067
1068create_tables.sql
1069\begin_inset Quotes erd
1070\end_inset
1071
1072 needs to be in your current working directory).
923\begin_deeper 1073\begin_deeper
924\layout LyX-Code 1074\layout LyX-Code
925 1075
1076mysql> use apachelogs
1077\layout LyX-Code
1078
1079Database changed
1080\layout LyX-Code
1081
926mysql> source create_tables.sql 1082mysql> source create_tables.sql
927\end_deeper 1083\end_deeper
928\layout Enumerate 1084\layout Enumerate
@@ -939,9 +1095,7 @@ Create a specific MySQL userid that httpd will use to authenticate and enter
939\layout LyX-Code 1095\layout LyX-Code
940 1096
941mysql> grant insert,create on apachelogs.* to loguser@my.apachemachine.com 1097mysql> grant insert,create on apachelogs.* to loguser@my.apachemachine.com
942\layout LyX-Code 1098 identified by 'l0gger';
943
944identified by 'l0gger';
945\end_deeper 1099\end_deeper
946\layout Enumerate 1100\layout Enumerate
947 1101
@@ -4817,24 +4971,29 @@ Context: virtual host
4817In HTTP, cookies have names to distinguish them from each other. 4971In HTTP, cookies have names to distinguish them from each other.
4818 Using mod_usertrack, for example, you can give your user-tracking cookies 4972 Using mod_usertrack, for example, you can give your user-tracking cookies
4819 a name with the CookieName directive. 4973 a name with the CookieName directive.
4974
4820\layout Standard 4975\layout Standard
4821 4976
4822You must include a 'c' character in 4977mod_log_sql allows you to log cookie information.
4823\noun on 4978
4824LogSQLTransferLogFormat
4825\noun default
4826 for this directive to take effect; once you specify 'c',
4827\noun on 4979\noun on
4828LogSQLWhichCookie 4980 LogSQLWhichCookie
4829\noun default 4981\noun default
4830 tells mod_log_sql which cookie to log. 4982 tells mod_log_sql which cookie to log.
4831 This is necessary because you will usually be setting and receiving more 4983 This is necessary because you will usually be setting and receiving more
4832 than one cookie from a client; this cookie designates which one to log. 4984 than one cookie from a client.
4985\layout Standard
4986
4987You must include a 'c' character in
4988\noun on
4989LogSQLTransferLogFormat
4990\noun default
4991 for this directive to take effect.
4833\layout Standard 4992\layout Standard
4834 4993
4835Note: although this was intended for people who are using mod_usertrack 4994Note: although this was origintally intended for people using mod_usertrack
4836 to set user-tracking cookies, you aren't restricted in any way. 4995 to create user-tracking cookies, you aren't restricted in any way.
4837 You can choose which cookie you wish to log to the database --any cookie 4996 You can choose which cookie you wish to log to the database -- any cookie
4838 at all -- and it doesn't necessarily have to have anything to do with mod_usert 4997 at all -- and it doesn't necessarily have to have anything to do with mod_usert
4839rack. 4998rack.
4840\layout Subsubsection 4999\layout Subsubsection
@@ -5742,7 +5901,7 @@ Please contact
5742Problems 5901Problems
5743\layout Subsubsection 5902\layout Subsubsection
5744 5903
5745Apache segfaults when using PHP and mod_log_sql 5904Apache segfaults or has other problems when using PHP and mod_log_sql
5746\layout Standard 5905\layout Standard
5747 5906
5748This occurs if you compiled PHP with MySQL database support. 5907This occurs if you compiled PHP with MySQL database support.
@@ -5759,8 +5918,9 @@ real
5759 5918
5760\layout Standard 5919\layout Standard
5761 5920
5762The solution is to configure PHP to link against the real MySQL libraries 5921PHP and mod_log_sql can be configured to happily coexist.
5763 and recompile mod_php. 5922 The solution is to configure PHP to link against the real MySQL libraries:
5923 recompile PHP using --with-mysql=/your/path.
5764 Apache will run properly once the modules are all using the same version 5924 Apache will run properly once the modules are all using the same version
5765 of the MySQL libraries. 5925 of the MySQL libraries.
5766\layout Subsubsection 5926\layout Subsubsection
@@ -5983,6 +6143,83 @@ Reference:
5983\end_inset 6143\end_inset
5984 6144
5985 6145
6146\layout Subsubsection
6147
6148Sometimes a single VirtualHost gets logged to two different tables (e.g.
6149 access_foo_com, access_www_foo_com).
6150 Or, accesses to an unqualified hostname (e.g.
6151
6152\begin_inset Quotes eld
6153\end_inset
6154
6155http://intranet/index.html
6156\begin_inset Quotes erd
6157\end_inset
6158
6159) get logged in separate tables.
6160\layout Standard
6161
6162Proper usage of the Apache runtime
6163\noun on
6164ServerName
6165\noun default
6166 directive and the directive
6167\noun on
6168UseCanonicalName On
6169\noun default
6170(or
6171\noun on
6172DNS
6173\noun default
6174) are necessary to prevent this problem.
6175
6176\begin_inset Quotes eld
6177\end_inset
6178
6179On
6180\begin_inset Quotes erd
6181\end_inset
6182
6183 is the default for
6184\noun on
6185UseCanonicalName
6186\noun default
6187, and specifies that self-referential URLs are generated from the
6188\noun on
6189ServerName
6190\noun default
6191part of your VirtualHost:
6192\layout Quote
6193
6194With UseCanonicalName on (and in all versions prior to 1.3) Apache will use
6195 the ServerName and Port directives to construct the canonical name for
6196 the server.
6197 With UseCanonicalName off Apache will form self-referential URLs using
6198 the hostname and port supplied by the client if any are supplied (otherwise
6199 it will use the canonical name, as defined above).
6200 [From
6201\begin_inset LatexCommand \url[the Apache documentation]{http://httpd.apache.org/docs/mod/core.html#usecanonicalname}
6202
6203\end_inset
6204
6205]
6206\layout Standard
6207
6208The module inherits Apache's
6209\begin_inset Quotes eld
6210\end_inset
6211
6212knowledge
6213\begin_inset Quotes erd
6214\end_inset
6215
6216 about the server name being accessed.
6217 As long as those two directives are properly configured, mod_log_sql will
6218 log to only one table per virtual host while using
6219\noun on
6220LogSQLMassVirtualHosting
6221\noun default
6222.
5986\layout Subsection 6223\layout Subsection
5987 6224
5988Performance and Tuning 6225Performance and Tuning
@@ -6236,7 +6473,7 @@ not
6236correct 6473correct
6237\series default 6474\series default
6238 to assume that 20 Apache children with a VSZ of 7MB each equals 6475 to assume that 20 Apache children with a VSZ of 7MB each equals
6239\begin_inset Formula $(20\times 7MB)$ 6476\begin_inset Formula $(20\times7MB)$
6240\end_inset 6477\end_inset
6241 6478
6242 of memory consumption -- the real answer is much, much lower. 6479 of memory consumption -- the real answer is much, much lower.
@@ -6359,74 +6596,6 @@ How do I...?
6359 -- accomplishing certain tasks 6596 -- accomplishing certain tasks
6360\layout Subsubsection 6597\layout Subsubsection
6361 6598
6362I am using LogSQLMassVirtualHosting, and sometimes a single VirtualHost
6363 gets logged to two different tables.
6364 How do I prevent that?
6365\layout Standard
6366
6367Proper usage of the Apache runtime
6368\noun on
6369ServerName
6370\noun default
6371 directive and the directive
6372\noun on
6373UseCanonicalName On
6374\noun default
6375(or
6376\noun on
6377DNS
6378\noun default
6379) are necessary to prevent this problem.
6380
6381\begin_inset Quotes eld
6382\end_inset
6383
6384On
6385\begin_inset Quotes erd
6386\end_inset
6387
6388 is the default for
6389\noun on
6390UseCanonicalName
6391\noun default
6392, and specifies that self-referential URLs are generated from the
6393\noun on
6394ServerName
6395\noun default
6396part of your VirtualHost:
6397\layout Quote
6398
6399With UseCanonicalName on (and in all versions prior to 1.3) Apache will use
6400 the ServerName and Port directives to construct the canonical name for
6401 the server.
6402 With UseCanonicalName off Apache will form self-referential URLs using
6403 the hostname and port supplied by the client if any are supplied (otherwise
6404 it will use the canonical name, as defined above).
6405 [From
6406\begin_inset LatexCommand \url[the Apache documentation]{http://httpd.apache.org/docs/mod/core.html#usecanonicalname}
6407
6408\end_inset
6409
6410]
6411\layout Standard
6412
6413The module inherits Apache's
6414\begin_inset Quotes eld
6415\end_inset
6416
6417knowledge
6418\begin_inset Quotes erd
6419\end_inset
6420
6421 about the server name being accessed.
6422 As long as those two directives are properly configured, mod_log_sql will
6423 log to only one table per virtual host while using
6424\noun on
6425LogSQLMassVirtualHosting
6426\noun default
6427.
6428\layout Subsubsection
6429
6430How do I extract the data in a format that my analysis tool can understand? 6599How do I extract the data in a format that my analysis tool can understand?
6431\layout Standard 6600\layout Standard
6432 6601
diff --git a/docs/manual.xml b/docs/manual.xml
new file mode 100644
index 0000000..9019e80
--- /dev/null
+++ b/docs/manual.xml
@@ -0,0 +1,3927 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<?xml-stylesheet href="file://localhost/home/urkle/Documents/DocBook/docbook.css" type="text/css"?>
3<!DOCTYPE article PUBLIC "-//OOOCC//DTD Simplified DocBook XML V1.1 Variant V1.0//EN" "http://outoforder.cc/dtds/odocbook/1.1/odocbook.dtd" [
4<!ENTITY EmailContact "<email>urkle &lt;at&gt; outoforder &lt;dot&gt; cc</email>">
5]>
6<article>
7 <articleinfo>
8 <title>mod_log_sql Manual</title>
9 <author>
10 <firstname>Edward</firstname>
11 <surname>Rudd</surname>
12 <contrib>Conversion from Lyx to DocBook</contrib>
13 <contrib>Current Maintainer</contrib>
14 <authorblurb>
15 <simpara>
16 &EmailContact;
17 </simpara>
18 </authorblurb>
19 </author>
20 <author>
21 <firstname>Christopher</firstname>
22 <othername>B.</othername>
23 <surname>Powell</surname>
24 <contrib>Original documentation author.</contrib>
25 <authorblurb>
26 <simpara>
27 <email>chris &lt;at&gt; grubbybaby &lt;dot&gt; com</email>
28 </simpara>
29 </authorblurb>
30 </author>
31 <copyright>
32 <year>2001</year>
33 <year>2002</year>
34 <year>2003</year>
35 <holder>Christopher B. Powell</holder>
36 </copyright>
37 <copyright>
38 <year>2004</year>
39 <year>2005</year>
40 <year>2006</year>
41 <holder>Edward Rudd</holder>
42 </copyright>
43 <revhistory>
44 <revision>
45 <revnumber>1.5</revnumber>
46 <date>2006-11-04</date>
47 <revremark>Added documentation about logio parameters and added DBParam Mysql driver parameters (including tabletype)</revremark>
48 </revision>
49 <revision>
50 <revnumber>1.4</revnumber>
51 <date>2006-02-13</date>
52 <revremark>Added missing logformat types, switched to simplified docbook 1.1</revremark>
53 </revision>
54 <revision>
55 <revnumber>1.3</revnumber>
56 <date>2005-01-11</date>
57 <revremark>Updated for mod_log_sql v1.100</revremark>
58 </revision>
59 <revision>
60 <revnumber>1.2</revnumber>
61 <date>2004-04-08</date>
62 <revremark>Updated for mod_log_sql v1.97</revremark>
63 </revision>
64 <revision>
65 <revnumber>1.1</revnumber>
66 <date>2004-03-02</date>
67 <revremark>Updated for mod_log_sql v1.96</revremark>
68 </revision>
69 <revision>
70 <revnumber>1.0</revnumber>
71 <date>2004-01-22</date>
72 <revremark>Initial Conversion from Lyx to Docbook</revremark>
73 </revision>
74 </revhistory>
75 </articleinfo>
76 <section>
77 <title>Introduction</title>
78 <section tocstyle="fragment">
79 <title>Summary</title>
80 <para>
81 This Apache module will permit you to log to a SQL database; it
82 can log each access request as well as data associated with each
83 request: cookies, notes, and inbound/outbound headers. Unlike
84 logging to a flat text file -- which is standard in Apache -- a
85 SQL-based log exhibits tremendous flexibility and power of data
86 extraction. (See FAQ entry
87 <xref linkend="FAQ.WhyLogToSQL" />
88 for further discussion and examples of the advantages to SQL.)
89 </para>
90 <para>
91 This module can either replace or happily coexist with
92 mod_log_config, Apache's text file logging facility. In addition
93 to being more configurable than the standard module, mod_log_sql
94 is much more flexible.
95 </para>
96 </section>
97 <section tocstyle="fragment">
98 <title>Approach</title>
99 <para>
100 This project was formerly known as "mod_log_mysql." It was
101 renamed "mod_log_sql" in order to reflect the project goal of
102 database in-specificity. The module currently supports MySQL,
103 but support for other database back-ends is underway.
104 </para>
105 <para>
106 In order to save speed and overhead, links are kept alive in
107 between queries. This module uses one dedicated SQL link per
108 httpd child, opened by each child process when it is born. Among
109 other things, this means that this module supports logging into
110 only one MySQL server, and for now, also, only one SQL database.
111 But that's a small tradeoff compared to the blinding speed of
112 this module. Error reporting is robust throughout the module and
113 will inform the administrator of database issues in the Apache
114 ErrorLog for the server/virtual server.
115 </para>
116 <para>
117 Virtual hosts are supported in the same manner they are in the
118 regular logging modules. The administrator defines some basic
119 'global' directives in the main server config, then defines more
120 specific 'local' directives inside each VirtualHost stanza.
121 </para>
122 <para>
123 A robust "preserve" capability has now been implemented. This
124 permits the module to preserve any failed INSERT commands to a
125 local file on its machine. In any situation that the database is
126 unavailable -- e.g. the network fails or the database host is
127 rebooted -- mod_log_sql will note this in the error log and
128 begin appending its log entries to the preserve file (which is
129 created with the user and group ID of the running Apache
130 process, e.g. "nobody/nobody" on many Linux installations). When
131 database availability returns, mod_log_sql seamlessly resumes
132 logging to it. When convenient for the sysadmin, he/she can
133 easily import the preserve file into the database because it is
134 simply a series of SQL insert statements.
135 </para>
136 </section>
137 <section tocstyle="fragment">
138 <title>What gets logged by default?</title>
139 <para>
140 All the data that would be contained in the "Combined Log
141 Format" is logged by default, plus a little extra. Your best bet
142 is to begin by accepting this default, then later customize the
143 log configuration based on your needs. The documentation of the
144 run-time directives includes a full explanation of what you can
145 log, including examples -- see section
146 <xref endterm="Sect.ConfigReference.title"
147 linkend="Sect.ConfigReference" />
148 .
149 </para>
150 </section>
151 <section tocstyle="fragment">
152 <title>Miscellaneous Notes</title>
153 <itemizedlist>
154 <listitem>
155 <para>
156 Note which directives go in the 'main server config' and
157 which directives apply to the 'virtual host config'. This is
158 made clear in the directive documentation.
159 </para>
160 </listitem>
161 <listitem>
162 <para>
163 The 'time_stamp' field is stored in an UNSIGNED INTEGER
164 format, in the standard unix "seconds since the epoch"
165 format. This is superior to storing the access time as a
166 string due to size requirements: an UNSIGNED INT requires 4
167 bytes, whereas an Apache date string (e.g.
168 "18/Nov/2001:13:59:52 -0800") requires 26 bytes: those extra
169 22 bytes become significant when multiplied by thousands of
170 accesses on a busy server. Besides, an INT type is far more
171 flexible for comparisons, etc.
172 </para>
173 <para>
174 In MySQL 3.21 and above you can easily convert this to a
175 human readable format using from_unixtime(), e.g.:
176 </para>
177 <programlisting>SELECT remote_host,request_uri,from_unixtime(time_stamp)
178FROM access_log;</programlisting>
179 <para>
180 The enclosed perl program "make_combined_log.pl" extracts
181 your access log in a format that is completely compatible
182 with the Combined Log Format. You can then feed this to your
183 favorite web log analysis tool.
184 </para>
185 </listitem>
186 <listitem>
187 <para>
188 The table's string values can be CHAR or VARCHAR, at a
189 length of your choice. VARCHAR is superior because it
190 truncates long strings; CHAR types are fixed-length and will
191 be padded with spaces, resulting in waste. Just like the
192 time_stamp issue described above, that kind of space waste
193 multiplies over thousands of records.
194 </para>
195 </listitem>
196 <listitem>
197 <para>
198 Be careful not to go overboard setting fields to NOT NULL.
199 If a field is marked NOT NULL then it must contain data in
200 the INSERT statement, or the INSERT will fail. These
201 mysterious failures can be quite frustrating and difficult
202 to debug.
203 </para>
204 </listitem>
205 <listitem>
206 <para>
207 When Apache logs a numeric field, it uses a '-' character to
208 mean "not applicable," e.g. the number of bytes returned on
209 a 304 (unchanged) request. Since '-' is an illegal character
210 in an SQL numeric field, such fields are assigned the value
211 0 instead of '-' which, of course, makes perfect sense
212 anyway.
213 </para>
214 </listitem>
215 </itemizedlist>
216 </section>
217 <section tocstyle="fragment">
218 <title>Author / Maintainer</title>
219 <para>
220 The actual logging code was taken from the already existing flat
221 file text modules, so all that credit goes to the Apache
222 Software Foundation.
223 </para>
224 <para>
225 The MySQL routines and directives were added by Zeev Suraski
226 &lt;bourbon@netvision.net.il&gt;.
227 </para>
228 <para>
229 All changes from 1.06+ and the new documentation were added by
230 Chris Powell
231 <email>chris &lt;at&gt; grubbybaby &lt;dot&gt; com</email>
232 . It seems that the module had fallen into the "un-maintained"
233 category -- it had not been updated since 1998 -- so Chris
234 adopted it as the new maintainer.
235 </para>
236 <para>
237 In December of 2003, Edward Rudd
238 &EmailContact;
239 porting the module to Apache 2.0, cleaning up the code,
240 converting the documentation to DocBook, optimizing the main
241 logging loop, and added the much anticipated database
242 abstraction layer.
243 </para>
244 <para>
245 As of February 2004, Chris Powell handed over maintenance of the
246 module over to Edward Rudd. So you should contact Edward Rudd
247 about the module from now on.
248 </para>
249 </section>
250 <section id="Sect.MailingLists" tocstyle="fragment">
251 <title id="Sect.MailingLists.title">Mailing Lists</title>
252 <para>
253 A general discussion and support mailing list is provided for
254 mod_log_sq at lists.outoforder.cc. To subscribe to the mailing
255 list send a blank e-mail to
256 mod_log_sql-subscribe@lists.outoforder.cc. The list archives can
257 be accessed via Gmane.org's mailng list gateway via any new
258 reader
259 <ulink
260 url="news://news.gmane.org/gmane.comp.apache.mod-log-sql">
261 news://news.gmane.org/gmane.comp.apache.mod-log-sql
262 </ulink>
263 , or via a web browser at
264 <ulink
265 url="http://news.gmane.org/gmane.comp.apache.mod-log-sql">
266 http://news.gmane.org/gmane.comp.apache.mod-log-sql
267 </ulink>
268 .
269 </para>
270 </section>
271 </section>
272 <section>
273 <title>Installation</title>
274 <section tocstyle="fragment">
275 <title>Requirements</title>
276 <itemizedlist>
277 <listitem>
278 <para>
279 A compatible system. mod_log_sql was authored and tested on
280 systems based on Red Hat Linux (Red Hat, Mandrake), but the
281 module should easily adapt to any modern distribution.
282 mod_log_sql has also been ported successfully to Solaris and
283 FreeBSD.
284 </para>
285 </listitem>
286 <listitem>
287 <para>
288 Apache 1.3 or 2.0, 1.2 is no longer supported, but may still
289 compile. Ideally you should already have successfully
290 compiled Apache and understand the process, but this
291 document tries to make it simple for beginners.
292 </para>
293 </listitem>
294 <listitem>
295 <para>
296 The MySQL development headers. This package is called
297 different things on different distributions. For example,
298 Red Hat 6.x calls this RPM "MySQL-devel" whereas Mandrake
299 calls it "libmysql10-devel." Both MySQL 3.23.x and 4.x are
300 supported.
301 </para>
302 </listitem>
303 <listitem>
304 <para>
305 MySQL &gt;= 3.23.15 configured, installed and running on
306 either localhost or an accessible networked machine. You
307 should already have a basic understanding of MySQL and how
308 it functions.
309 </para>
310 </listitem>
311 <listitem>
312 <para>
313 Optionally, if you want to be able to log SSL information
314 such as keysize or cipher, you need OpenSSL and mod_ssl
315 installed.
316 </para>
317 </listitem>
318 </itemizedlist>
319 </section>
320 <section>
321 <title>Compiling and Installing</title>
322 <orderedlist>
323 <listitem>
324 <para>Unpack the archive into a working directory.</para>
325 <programlisting>$ tar -xzf mod_log_sql-1.94.tar.gz
326$ cd mod_log_sql-1.94</programlisting>
327 </listitem>
328 <listitem>
329 <para>run configure to configure the source directory.</para>
330 <programlisting>$ ./configure</programlisting>
331 <para>
332 The
333 <filename>configure</filename>
334 script should automatically detect all the required
335 libraries and program if the are installed in standard
336 locations.. If it returns an error, here is a description of
337 the arguments you can specify when you run
338 <filename>configure</filename>
339 .
340 </para>
341 <variablelist>
342 <varlistentry>
343 <term>--with-apxs=/usr/sbin/apxs</term>
344 <listitem>
345 <para>
346 This is the full path to the apxs binary, or the
347 directory which contains the program. This program is
348 part of the Apache 1.3 and 2.0 installation.
349 </para>
350 <para>
351 The default is to search
352 <filename>/usr/bin/apxs</filename>
353 and
354 <filename>/usr/sbin/apxs</filename>
355 .
356 </para>
357 <para>
358 Specifying a directory here will search
359 $directory/apxs, $directory/bin/apxs, and
360 $directory/sbin/apxs
361 </para>
362 <para>
363 If you have more than one version of Apache installed,
364 you need to specify the correct apxs binary for the
365 one you wish to compile for.
366 </para>
367 </listitem>
368 </varlistentry>
369 <varlistentry>
370 <term>--with-mysql=/path/to/mysql</term>
371 <listitem>
372 <para>
373 This is the directory to search for the
374 <filename>libmysqlclient</filename>
375 library and the
376 <application>MySQL</application>
377 headers.
378 </para>
379 <para>
380 The default is to search
381 <filename>/usr/include</filename>
382 ,
383 <filename>/usr/include/mysql</filename>
384 ,
385 <filename>/usr/local/include</filename>
386 , and
387 <filename>/usr/local/include/mysql</filename>
388 for
389 <application>MySQL</application>
390 headers.. And
391 <filename>/usr/lib</filename>
392 .
393 <filename>/usr/lib/mysql</filename>
394 ,
395 <filename>/usr/local/lib</filename>
396 , and
397 <filename>/usr/local/lin/mysql</filename>
398 for the
399 <application>MySQL</application>
400 libraries.
401 </para>
402 <para>
403 Specifying this testargument will search
404 $directory/include and $directory/mysql for
405 <application>MySQL</application>
406 headers. And $directory/lib and $directory/lib/mysql
407 for
408 <application>MySQL</application>
409 libraries.
410 </para>
411 </listitem>
412 </varlistentry>
413 <varlistentry>
414 <term>--enable-ssl</term>
415 <listitem>
416 <para>
417 Specifying this argument will enable the search for
418 mod_ssl and SSL headers, and if found will enable
419 compilation of SSL support into mod_log_sql. SSL
420 support is compiled into a separate module that can be
421 loaded after the main mod_log_sql.
422 </para>
423 </listitem>
424 </varlistentry>
425 <varlistentry>
426 <term>--with-ssl-inc=/usr/include/openssl</term>
427 <listitem>
428 <para>
429 This is the path to the SSL toolkit header files that
430 were used to compile mod_ssl. If you want SSL support
431 you most likely need to specify this.
432 </para>
433 <para>
434 The default is to search
435 <filename>/usr/include</filename>
436 and
437 <filename>/usr/include/openssl</filename>
438 .
439 </para>
440 <para>
441 Specifying this argument will search that directory
442 for the SSL headers.
443 </para>
444 </listitem>
445 </varlistentry>
446 <varlistentry>
447 <term>--with-db-inc=/usr/include/db1</term>
448 <listitem>
449 <para>
450 This argument is only needed when compiling SSL
451 support for Apache 1.3, and needs to be the directory
452 which contains the ndbm.h header file. You can find
453 this by using
454 </para>
455 <programlisting>$ locate ndbm.h
456/usr/include/db1/ndbm.h
457/usr/include/gdbm/ndbm.h</programlisting>
458 <para>
459 As far as I can tell, there is no difference as to
460 which you specify, but it should be the one that you
461 compiled mod_ssl with.
462 </para>
463 <para>
464 The default is
465 <filename>/usr/include/db1</filename>
466 , which should work on most systems.
467 </para>
468 </listitem>
469 </varlistentry>
470 <varlistentry>
471 <term>--disable-apachetest</term>
472 <listitem>
473 <para>
474 This will disable the apache version test. However
475 there is a side affect if you specify this where I
476 will not be able to determine which version of Apache
477 you are compiling for. So don't specify this.. If you
478 are having troubles with the script detecting your
479 Apache version, then send a bug report along with your
480 system OS version and versions of related packages.
481 </para>
482 </listitem>
483 </varlistentry>
484 <varlistentry>
485 <term>--disable-mysqltest</term>
486 <listitem>
487 <para>
488 This will disable the MySQL compile test. Specify this
489 if for some reason the test fail but you know you have
490 specified the correct directories. If mod_los_sql also
491 fails to compile report a bug along with your system
492 OS version and versions of related packages.
493 </para>
494 </listitem>
495 </varlistentry>
496 </variablelist>
497 </listitem>
498 <listitem>
499 <para>
500 Now compile the module with GNU make. You may have to
501 specify gmake on some systems like FreeBSD.
502 </para>
503 <programlisting>$ gmake</programlisting>
504 </listitem>
505 <listitem>
506 <para>
507 If there were no errors, you can now install the module(s).
508 If you compiled as a non-root user you may need to switch
509 users with
510 <application>su</application>
511 or
512 <application>sudo</application>
513 .
514 </para>
515 <programlisting>$ su -c "gmake install"
516Password:</programlisting>
517 </listitem>
518 <listitem>
519 <para>
520 Now edit your Apache configuration and load the modules.
521 </para>
522 <note>
523 <itemizedlist>
524 <listitem>
525 <para>
526 If you are loading the SSL logging module, you need to
527 make sure it is loaded after mod_ssl and mod_log_sql.
528 </para>
529 </listitem>
530 <listitem>
531 <para>
532 If you have previously used mod_log_sql version 1.18,
533 the name of the module has changed from sql_log_module
534 to log_sql_module (the first parameter to LoadModule)
535 </para>
536 </listitem>
537 <listitem>
538 <para>
539 If you are upgrading from any release earlier than
540 1.97 you need to add an extra LoadModule directive to
541 load the database driver (ie mysql).
542 </para>
543 </listitem>
544 </itemizedlist>
545 </note>
546 <orderedlist>
547 <listitem>
548 <para>
549 Insert these lines to either the main
550 <filename>httpd.conf</filename>
551 or a file included via an include directive.
552 </para>
553 <programlisting>LoadModule log_sql_module modules/mod_log_sql.so
554LoadModule log_sql_mysql_module modules/mod_log_sql_mysql.so
555&lt;IfModule mod_ssl.c&gt;
556LoadModule log_sql_ssl_module moduels/mod_log_sql_ssl.so
557&lt;/IfModule&gt;</programlisting>
558 <note>
559 <para>
560 If you did not compile SSL support in mod_log_sql, do
561 not include the lines between the &lt;IfModule&gt;
562 directives.
563 </para>
564 </note>
565 </listitem>
566 <listitem>
567 <para>
568 If you are using Apache 1.3 you may need add these lines
569 later in the configuration.
570 </para>
571 <programlisting>AddModule mod_log_sql.c
572AddModule mod_log_sql_mysql.c
573&lt;IfModule mod_ssl.c&gt;
574AddModule mod_log_sql_ssl.c
575&lt;/IfModule&gt;</programlisting>
576 <note>
577 <para>
578 If you did not compile SSL support in mod_log_sql, do
579 not include the lines between the &lt;IfModule&gt;
580 directives.
581 </para>
582 </note>
583 </listitem>
584 </orderedlist>
585 </listitem>
586 </orderedlist>
587 </section>
588 </section>
589 <section id="Sect.Configuration">
590 <title id="Sect.Configuration.title">Configuration</title>
591 <section id="Sect.Preperation">
592 <title id="Sect.Preperation.title">
593 Preparing MySQL for logging
594 </title>
595 <para>
596 You have to prepare the database to receive data from
597 <application>mod_log_sql</application>
598 , and set up run-time directives in
599 <filename>httpd.conf</filename>
600 to control how and what
601 <application>mod_log_sql</application>
602 logs.
603 </para>
604 <para>
605 This section will discuss how to get started with a basic
606 configuration. Full documentation of all available run-time
607 directives is available in section
608 <xref endterm="Sect.ConfigReference.title"
609 linkend="Sect.ConfigReference" />
610 .
611 </para>
612 <orderedlist>
613 <listitem>
614 <para>
615 mod_log_sql can make its own tables on-the-fly, or you can
616 pre-make the tables by hand. The advantage of letting the
617 module make the tables is ease-of-use, but for raw
618 performance you will want to pre-make the tables in order to
619 save some overhead. In this basic setup we'll just let the
620 module create tables for us.
621 </para>
622 </listitem>
623 <listitem>
624 <para>
625 We still need to have a logging database created and ready,
626 so run the MySQL command line client and create a database:
627 </para>
628 <programlisting># mysql -uadmin -p
629Enter password:
630mysql&gt; create database apachelogs;</programlisting>
631 </listitem>
632 <listitem id="Item.CreateTable">
633 <para>
634 If you want to hand-create the tables, run the enclosed
635 'create-tables' SQL script as follows ("create_tables.sql"
636 needs to be in your current working directory).
637 </para>
638 <programlisting>mysql&gt; use apachelogs
639Database changed
640mysql&gt; source create_tables.sql</programlisting>
641 </listitem>
642 <listitem>
643 <para>
644 Create a specific
645 <application>MySQL</application>
646 userid that
647 <application>httpd</application>
648 will use to authenticate and enter data. This userid need
649 not be an actual Unix user. It is a userid internal to
650 <application>MySQL</application>
651 with specific privileges. In the following example command,
652 "apachelogs" is the database, "loguser" is the userid to
653 create, "my.apachemachine.com" is the name of the Apache
654 machine, and "l0gger" is the password to assign. Choose
655 values that are different from these examples.
656 </para>
657 <programlisting>mysql&gt; grant insert,create on apachelogs.* to loguser@my.apachemachine.com identified by 'l0gger';</programlisting>
658 </listitem>
659 <listitem>
660 <para>
661 You may be especially security-paranoid and want "loguser"
662 to not have "create" capability within the "apachelogs"
663 database. You can disable that privilege, but the cost is
664 that you will not be able to use the module's on-the-fly
665 table creation feature. If that cost is acceptable,
666 hand-create the tables as described in step
667 <xref linkend="Item.CreateTable" />
668 and use the following GRANT statement instead of the one
669 above:
670 </para>
671 <programlisting>mysql&gt; grant insert on apachelogs.* to loguser@my.apachemachine.com identified by 'l0gger';</programlisting>
672 </listitem>
673 <listitem id="Item.EnableLogging">
674 <para>
675 Enable full logging of your
676 <application>MySQL</application>
677 daemon (at least temporarily for debugging purposes) if you
678 don't do this already. Edit /etc/my.cnf and add the
679 following line to your [mysqld] section:
680 </para>
681 <programlisting>log=/var/log/mysql-messages</programlisting>
682 <para>
683 Then restart
684 <application>MySQL</application>
685 </para>
686 <programlisting># /etc/rc.d/init.d/mysql restart</programlisting>
687 </listitem>
688 </orderedlist>
689 </section>
690 <section>
691 <title>A very basic logging setup in Apache</title>
692 <orderedlist>
693 <listitem>
694 <para>
695 Tell the module what database to use and the appropriate
696 authentication information.
697 </para>
698 <para>
699 So, edit httpd.conf and insert the following lines somewhere
700 after any LoadModule / AddModule statements. Make sure these
701 statements are "global," i.e. not inside any VirtualHost
702 stanza. You will also note that you are embedding a password
703 in the file. Therefore you are advised to "chmod 660
704 httpd.conf" to prevent unauthorized regular users from
705 viewing your database user and password.
706 </para>
707 <para>
708 Use the
709 <application>MySQL</application>
710 database called "apachelogs" running on "dbmachine.foo.com".
711 Use username "loguser" and password "l0gg3r" to authenticate
712 to the database. Permit the module create tables for us.
713 </para>
714 <example>
715 <title>Basic Example</title>
716 <programlisting>LogSQLLoginInfo mysql://loguser:l0gg3r@dbmachine.foo.com/apachelogs
717LogSQLCreateTables on</programlisting>
718 </example>
719 <para>
720 If your database resides on localhost instead of another
721 host, specify the MySQL server's socket file as follows:
722 </para>
723 <programlisting>LogSQLDBParam socketfile /your/path/to/mysql.sock</programlisting>
724 <para>
725 If your database is listening on a port other than 3306,
726 specify the correct TCP port as follows:
727 </para>
728 <programlisting>LogSQLDBParam port 1234</programlisting>
729 </listitem>
730 <listitem>
731 <para>
732 The actual logging is set up on a virtual-host-by-host
733 basis. So, skip down to the virtual host you want to set up.
734 Instruct this virtual host to log entries to the table
735 "access_log" by inserting a LogSQLTransferLogTable
736 directive. (The LogSQLTransferLogTable directive is the
737 minimum required to log -- other directives that you will
738 learn about later simply tune the module's behavior.)
739 </para>
740 <programlisting>&lt;VirtualHost 1.2.3.4&gt;
741 [snip]
742 LogSQLTransferLogTable access_log
743 [snip]
744&lt;/VirtualHost&gt;</programlisting>
745 </listitem>
746 <listitem>
747 <para>Restart apache.</para>
748 <programlisting># /etc/rc.d/init.d/httpd stop
749# /etc/rc.d/init.d/httpd start</programlisting>
750 </listitem>
751 </orderedlist>
752 </section>
753 <section>
754 <title>Testing the basic setup</title>
755 <orderedlist>
756 <listitem>
757 <para>
758 Visit your web site in a browser to trigger some hits, then
759 confirm that the entries are being successfully logged:
760 </para>
761 <programlisting># mysql -hdbmachine.foo.com -umysqladmin -p -e "SELECT * FROM access_log" apachelogs
762Enter password:</programlisting>
763 <para>
764 Several lines of output should follow, corresponding to your
765 hits on the site. You now have basic functionality. Don't
766 disable your regular Apache logs until you feel comfortable
767 that the database is behaving as you'd like and that things
768 are going well. If you do not see any entries in the
769 access_log, please consult section
770 <xref linkend="FAQ.NothingLogged" />
771 of the FAQ on how to debug and fix the situation.
772 </para>
773 </listitem>
774 <listitem>
775 <para>
776 You can now activate the advanced features of mod_log_sql,
777 which are described in the next section.
778 </para>
779 </listitem>
780 </orderedlist>
781 </section>
782 <section>
783 <title>How to tune logging with run-time directives</title>
784 <section tocstyle="fragment">
785 <title>Instructing the module what to log</title>
786 <para>
787 The most basic directive for the module is
788 LogSQLTransferLogFormat, which tells the module which
789 information to send to the database; logging to the database
790 will not take place without it. Place a
791 LogSQLTransferLogFormat directive in the VirtualHost stanza of
792 each virtual host that you want to activate.
793 </para>
794 <para>
795 After LogSQLTransferLogFormat you supply a string of
796 characters that tell the module what information to log. In
797 the configuration directive reference (section
798 <xref linkend="Conf.LogSQLTransferLogFormat" />
799 ) there is a table which clearly defines all the possible
800 things to log. Let's say you want to log only the "request
801 time," the "remote host," and the "request"; you'd use:
802 </para>
803 <programlisting>LogSQLTransferLogFormat hUS</programlisting>
804 <para>But a more appropriate string to use is</para>
805 <programlisting>LogSQLTransferLogFormat AbHhmRSsTUuv</programlisting>
806 <para>
807 which logs all the information required to be compatible with
808 the Combined Log Format (CLF).
809 </para>
810 <para>
811 If you don't choose to log everything that is available,
812 that's fine. Fields in the unused columns in your table will
813 simply contain NULL.
814 </para>
815 <para>
816 Some of the LogSQLTransferLogFormat characters require a
817 little extra configuration:
818 </para>
819 <itemizedlist>
820 <listitem>
821 <para>
822 If you specify 'c' to indicate that you want to log the
823 cookie value, you must also tell the module which cookie
824 you mean by using LogSQLWhichCookie -- after all, there
825 could be many cookies associated with a given request.
826 Fail to specify LogSQLWhichCookie, and no cookie
827 information at all will be logged.
828 </para>
829 </listitem>
830 <listitem>
831 <para>
832 If you specify 'M' to indicate that you want to log the
833 machine ID, you must also tell the module this machine's
834 identity using the LogSQLMachineID directive. Fail to
835 specify LogSQLMachineID, and a simple '-' character will
836 be logged in the machine_id column.
837 </para>
838 </listitem>
839 </itemizedlist>
840 </section>
841 <section id="Sect.Ignore">
842 <title id="Sect.Ignore.title">
843 Instructing the module what NOT to log using filtering
844 directives
845 </title>
846 <para>
847 One "accept" and two "ignore" directives allow you to
848 fine-tune what the module should not log. These are very handy
849 for keeping your database as uncluttered as possible and
850 keeping your statistics free of unneeded numbers. Think of
851 each one as a gatekeeper.
852 </para>
853 <para>
854 <emphasis>
855 It is important to remember that each of these three
856 directives is purely optional. mod_log_sql's default is to
857 log everything.
858 </emphasis>
859 </para>
860 <para>
861 When a request comes in, the contents of LogSQLRequestAccept
862 are evaluated first. This optional, "blanket" directive lets
863 you specify that only certain things are to be accepted for
864 logging, and everything else discarded. Because it is
865 evaluated before LogSQLRequestIgnore and LogSQLRemhostIgnore
866 it can halt logging before those two filtering directives "get
867 their chance."
868 </para>
869 <para>
870 Once a request makes it past LogSQLRequestAccept, it still can
871 be excluded based on LogSQLRemhostIgnore and
872 LogSQLRequestIgnore. A good way to use LogSQLRemhostIgnore is
873 to prevent the module from logging the traffic that your
874 internal hosts generate. LogSQLRequestIgnore is great for
875 preventing things like requests for "favicon.ico" from
876 cluttering up your database, as well as excluding the various
877 requests that worms make, etc.
878 </para>
879 <para>
880 You can specify a series of strings after each directive. Do
881 not use any type of globbing or regular-expression syntax --
882 each string is considered a match
883 <emphasis>
884 if it is a substring of the larger request or remote-host;
885 the comarison is case-sensitive
886 </emphasis>
887 . This means that "LogSQLRemhostIgnore micro" will ignore
888 requests from "microsoft.com," "microworld.net,"
889 "mymicroscope.org," etc. "LogSQLRequestIgnore gif" will
890 instruct the module to ignore requests for "leftbar.gif,"
891 "bluedot.gif" and even "giftwrap.jpg" -- but "RED.GIF" and
892 "Tree.Gif" would still get logged because of case sensitivity.
893 </para>
894 <para>A summary of the decision flow:</para>
895 <orderedlist>
896 <listitem>
897 <para>
898 If LogSQLRequestAccept exists and a request does not match
899 anything in that list, it is discarded.
900 </para>
901 </listitem>
902 <listitem>
903 <para>
904 If a request matches anything in the LogSQLRequestIgnore
905 list, it is discarded.
906 </para>
907 </listitem>
908 <listitem>
909 <para>
910 If a reqiest matches anything in the LogSQLRemhostIgnore
911 list, it is discarded.
912 </para>
913 </listitem>
914 <listitem>
915 <para>Otherwise the request is logged.</para>
916 </listitem>
917 </orderedlist>
918 <para>
919 This means that you can have a series of directives similar to
920 the following:
921 </para>
922 <programlisting>LogSQLRequestAccept .html .gif .jpg
923LogSQLRequestIgnore statistics.html bluedot.jpg</programlisting>
924 <para>
925 So the first line instructs the module to only log files with
926 html, gif and jpg suffixes; requests for "formail.cgi" and
927 "shopping-cart.pl" will never be considered for logging.
928 ("LeftArrow.JPG" will also never be considered for logging --
929 remember, the comparison is case sensitive.) The second line
930 prunes the list further -- you never want to log requests for
931 those two objects.
932 </para>
933 <note role="tip">
934 <itemizedlist>
935 <listitem>
936 <para>
937 If you want to match all the hosts in your domain such
938 as "host1.corp.foo.com" and "server.dmz.foo.com", simply
939 specify:
940 </para>
941 <programlisting>LogSQLRemhostIgnore foo.com</programlisting>
942 </listitem>
943 <listitem>
944 <para>
945 A great way to catch the vast majority of worm-attack
946 requests and prevent them from being logged is to
947 specify:
948 </para>
949 <programlisting>LogSQLRequestIgnore root.exe cmd.exe default.ida</programlisting>
950 </listitem>
951 <listitem>
952 <para>
953 To prevent the logging of requests for common graphic
954 types, make sure to put a '.' before the suffix to avoid
955 matches that you didn't intend:
956 </para>
957 <programlisting>LogSQLRequestIgnore .gif .jpg</programlisting>
958 </listitem>
959 </itemizedlist>
960 </note>
961 </section>
962 </section>
963 <section>
964 <title>Advanced logging scenarios</title>
965 <section tocstyle="fragment">
966 <title>Using the module in an ISP environment</title>
967 <para>mod_log_sql has three basic tiers of operation:</para>
968 <orderedlist>
969 <listitem>
970 <para>
971 The administrator creates all necessary tables by hand and
972 configures each Apache VirtualHost by hand.
973 (LogSQLCreateTables Off)
974 </para>
975 </listitem>
976 <listitem>
977 <para>
978 The module is permitted to create necessary tables
979 on-the-fly, but the administrator configures each Apache
980 VirtualHost by hand. (LogSQLCreateTables On)
981 </para>
982 </listitem>
983 <listitem>
984 <para>
985 The module is permitted to create all necessary tables and
986 to make intelligent, on-the-fly configuration of each
987 VirtualHost. (LogSQLMassVirtualHosting On)
988 </para>
989 </listitem>
990 </orderedlist>
991 <para>
992 Many users are happy to use the module in its most minimal
993 form: they hand-create any necessary tables (using
994 "create_tables.sql"), and they configure each VirtualHost by
995 hand to suit their needs. However, some administrators need
996 extra features due to a large and growing number of
997 VirtualHosts. The LogSQLMassVirtualHosting directive activates
998 module capabilities that make it far easier to manage an ISP
999 environment, or any situation characterized by a large and
1000 varying number of virtual servers.
1001 </para>
1002 <itemizedlist>
1003 <listitem>
1004 <para>
1005 the on-the-fly table creation feature is activated
1006 automatically
1007 </para>
1008 </listitem>
1009 <listitem>
1010 <para>
1011 the transfer log table name is dynamically set from the
1012 virtual host's name (example: a virtual host
1013 "www.grubbybaby.com" gets logged to table
1014 "access_www_grubbybaby_com")
1015 </para>
1016 </listitem>
1017 </itemizedlist>
1018 <para>
1019 There are numerous benefits. The admin will not need to create
1020 new tables for every new VirtualHost. (Although the admin will
1021 still need to drop the tables of virtual hosts that are
1022 removed.) The admin will not need to set
1023 LogSQLTransferLogTable for each virtual host -- it will be
1024 configured automatically based on the host's name. Because
1025 each virtual host will log to its own segregated table, data
1026 about one virtual server will segregate from others; an admin
1027 can grant users access to the tables they need, and they will
1028 be unable to view data about another user's virtual host.
1029 </para>
1030 <para>
1031 In an ISP scenario the admin is likely to have a cluster of
1032 many front-end webservers logging to a back-end database.
1033 mod_log_sql has a feature that permits analysis of how well
1034 the web servers are loadbalancing: the LogSQLMachineID
1035 directive. The administrator uses this directive to assign a
1036 unique identifier to each machine in the web cluster, e.g.
1037 "LogSQLMachineID web01," "LogSQLMachineID web02," etc. Used in
1038 conjunction with the 'M' character in LogSQLTransferLogFormat,
1039 each entry in the SQL log will include the machine ID of the
1040 machine that created the entry. This permits the administrator
1041 to count the entries made by each particular machine and
1042 thereby analyze the front-end loadbalancing algorithm.
1043 </para>
1044 </section>
1045 <section id="Sect.MultiTable">
1046 <title id="Sect.MultiTable.title">
1047 Logging many-to-one data in separate tables
1048 </title>
1049 <para>
1050 A given HTTP request can have a one-to-many relationship with
1051 certain kinds of data. For example, a single HTTP request can
1052 have 4 cookies, 3 headers and 5 "mod_gzip" notes associated
1053 with it. mod_log_sql is capable of logging these relationships
1054 due to the elegance of SQL relational data.
1055 </para>
1056 <para>
1057 You already have a single table containing access requests.
1058 One of the columns in that table is 'id' which is intended to
1059 contain the unique request ID supplied by the standard Apache
1060 module mod_unique_id -- all you need to do is compile in that
1061 module and employ the LogSQLTransferLogFormat character 'I'.
1062 Thereafter, each request gets a unique ID that can be thought
1063 of as a primary key within the database, useful for joining
1064 multiple tables. So let's envision several new tables: a notes
1065 table, a cookies table, and a table for inbound and outbound
1066 headers.
1067 </para>
1068 <table>
1069 <title>&lt;tblAcc&gt;access_log</title>
1070 <tgroup cols="6">
1071 <colspec colname="1" />
1072 <colspec colname="2" />
1073 <colspec colname="3" />
1074 <colspec colname="4" />
1075 <colspec colname="5" colwidth="40" />
1076 <colspec colname="6" colwidth="70" />
1077 <thead>
1078 <row>
1079 <entry colname="1">id</entry>
1080 <entry colname="2">remote_host</entry>
1081 <entry colname="3">request_uri</entry>
1082 <entry colname="4">time_stamp</entry>
1083 <entry colname="5">status</entry>
1084 <entry colname="6">bytes_sent</entry>
1085 </row>
1086 </thead>
1087 <tbody>
1088 <row>
1089 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1090 <entry colname="2">zerberus.aiacs.net</entry>
1091 <entry colname="3">/mod_log_sql/index.html</entry>
1092 <entry colname="4">1022493617</entry>
1093 <entry colname="5">200</entry>
1094 <entry colname="6">2215</entry>
1095 </row>
1096 </tbody>
1097 </tgroup>
1098 </table>
1099 <table>
1100 <title>&lt;tblNotes&gt;notes_log</title>
1101 <tgroup cols="3">
1102 <colspec colname="1" />
1103 <colspec colname="2" />
1104 <colspec colname="3" colwidth="30" />
1105 <thead>
1106 <row>
1107 <entry colname="1">id</entry>
1108 <entry colname="2">item</entry>
1109 <entry colname="3">val</entry>
1110 </row>
1111 </thead>
1112 <tbody>
1113 <row>
1114 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1115 <entry colname="2">mod_gzip_result</entry>
1116 <entry colname="3">OK</entry>
1117 </row>
1118 <row>
1119 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1120 <entry colname="2">mod_gzip_compression_ratio</entry>
1121 <entry colname="3">69</entry>
1122 </row>
1123 </tbody>
1124 </tgroup>
1125 </table>
1126 <table>
1127 <title>&lt;tblHdr&gt;headers_log</title>
1128 <tgroup cols="3">
1129 <colspec colname="1" colnum="1" />
1130 <colspec colname="2" colnum="2" />
1131 <colspec colname="3" colnum="3" />
1132 <thead>
1133 <row>
1134 <entry colname="1">id</entry>
1135 <entry colname="2">item</entry>
1136 <entry colname="3">val</entry>
1137 </row>
1138 </thead>
1139 <tbody>
1140 <row>
1141 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1142 <entry colname="2">Content-Type</entry>
1143 <entry colname="3">text/html</entry>
1144 </row>
1145 <row>
1146 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1147 <entry colname="2">Accept-Encoding</entry>
1148 <entry colname="3">gzip, deflate</entry>
1149 </row>
1150 <row>
1151 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1152 <entry colname="2">Expires</entry>
1153 <entry colname="3">Tue, 28 May 2002 10:00:18 GMT</entry>
1154 </row>
1155 <row>
1156 <entry colname="1">PPIDskBRH30AAGPtAsg</entry>
1157 <entry colname="2">Cache-Control</entry>
1158 <entry colname="3">max-age=86400</entry>
1159 </row>
1160 </tbody>
1161 </tgroup>
1162 </table>
1163 <para>
1164 We have a certain request, and its unique ID is
1165 "PPIDskBRH30AAGPtAsg". Within each separate table will be
1166 multiple entries with that request ID: several cookie entries,
1167 several header entries, etc. As you can see in tables
1168 [tblAcc], [tblNotes] and [tblHdr], you have a one-to-many
1169 relationship for request PPIDskBRH30AAGPtAsg: that one access
1170 has two associated notes and four associated headers. You can
1171 extract this data easily using the power of SQL's "select"
1172 statement and table joins. To see the notes associated with a
1173 particular request:
1174 </para>
1175 <programlisting>SELECT a.remote_host, a.request_uri, n.item, n.val
1176FROM access_log a JOIN notes_log n ON a.id=n.id
1177WHERE a.id='PPIDskBRH30AAGPtAsg';</programlisting>
1178 <table>
1179 <title>access_log joined to notes_log</title>
1180 <tgroup cols="4">
1181 <colspec colname="1" />
1182 <colspec colname="2" />
1183 <colspec colname="3" />
1184 <colspec colname="4" colwidth="30" />
1185 <thead>
1186 <row>
1187 <entry colname="1">remote_host</entry>
1188 <entry colname="2">request_uri</entry>
1189 <entry colname="3">item</entry>
1190 <entry colname="4">val</entry>
1191 </row>
1192 </thead>
1193 <tbody>
1194 <row>
1195 <entry colname="1">zerberus.aiacs.net</entry>
1196 <entry colname="2">/mod_log_sql/index.html</entry>
1197 <entry colname="3">mod_gzip_result</entry>
1198 <entry colname="4">OK</entry>
1199 </row>
1200 <row>
1201 <entry colname="1">zerberus.aiacs.net</entry>
1202 <entry colname="2">/mod_log_sql/index.html</entry>
1203 <entry colname="3">mod_gzip_compression_ratio</entry>
1204 <entry colname="4">69</entry>
1205 </row>
1206 </tbody>
1207 </tgroup>
1208 </table>
1209 <para>
1210 Naturally you can craft similar statements for the outboud
1211 headers, inbound headers and cookies, all of which can live in
1212 separate tables. Your statements are limited in power only by
1213 your skill with SQL.
1214 </para>
1215 <para>
1216 In order to use this capability of mod_log_sql, you must do
1217 several things.
1218 </para>
1219 <itemizedlist>
1220 <listitem>
1221 <para>
1222 Compile mod_unique_id into Apache (statically or as a
1223 DSO). mod_log_sql employs the unique request ID that
1224 mod_unique_id provides in order to key between the
1225 separate tables. You can still log the data without
1226 mod_unqiue_id, but it will be completely uncorrelated and
1227 you will have no way to discern any meaning.
1228 </para>
1229 </listitem>
1230 <listitem>
1231 <para>
1232 Create the appropriate tables. This will be done for you
1233 if you permit mod_log_sql to create its own tables using
1234 LogSQLCreateTables On, or if you use the enclosed
1235 "create_tables.sql" script.
1236 </para>
1237 </listitem>
1238 <listitem>
1239 <para>
1240 Create a SQL index on the "id" column. Without this index,
1241 table joins will be deathly slow. I recommend you consult
1242 the MySQL documentation on the proper way to create a
1243 column index if you are not familiar with this operation.
1244 </para>
1245 </listitem>
1246 <listitem>
1247 <para>
1248 Within each appropriate VirtualHost stanza, use the
1249 LogSQLWhich* and LogSQL*LogTable directives to tell the
1250 module what and where to log the data. In the following
1251 example, I have overridden the name for the notes table
1252 whereas I have left the other table names at their
1253 defaults. I have then specified the cookies, headers and
1254 notes that interest me. (And as you can see, these
1255 directives do not require me to add any characters to
1256 LogSQLTransferLogTable.)
1257 </para>
1258 <programlisting>&lt;VirtualHost 216.231.36.128&gt;
1259 (snip)
1260 LogSQLNotesLogTable notestable
1261 LogSQLWhichCookies bluecookie redcookie greencookie
1262 LogSQLWhichNotes mod_gzip_result mod_gzip_compression_ratio
1263 LogSQLWhichHeadersOut Expires Content-Type Cache-Control
1264 LogSQLWhichHeadersIn User-Agent Accept-Encoding Host
1265 (snip)
1266&lt;/VirtualHost&gt;</programlisting>
1267 </listitem>
1268 </itemizedlist>
1269 </section>
1270 <section>
1271 <title>Using the same database for production and test</title>
1272 <para>
1273 Although sub-optimal, it is not uncommon to use the same
1274 back-end database for the "production" webservers as well as
1275 the "test" webservers (budgetary constraints, rack-space
1276 limits, etc.). Furthermore, an administrator in this situation
1277 may be unable to use LogSQLRemhostIgnore to exclude requests
1278 from the test servers -- perhaps the generated entries are
1279 genuinely useful for analytical or QA purposes, but their
1280 value after analysis is minimal.
1281 </para>
1282 <para>
1283 It is wasteful and potentially confusing to permit this
1284 internal test data to clutter the database, and a solution to
1285 the problem is the proper use of the LogSQLMachineID
1286 directive. Assume a scenario where the production webservers
1287 have IDs like "web01," "web02," and so on -- and the test
1288 webservers have IDs like "test01," "test02," etc. Because
1289 entries in the log database are distinguished by their source
1290 machine, an administrator may purge unneeded test data from
1291 the access log as follows:
1292 </para>
1293 <programlisting>DELETE FROM access_log WHERE machine_id like 'test%';</programlisting>
1294 </section>
1295 <section id="Sect.DelayedInsert">
1296 <title id="Sect.DelayedInsert.title">
1297 Optimizing for a busy database
1298 </title>
1299 <para>
1300 A busy MySQL database will have SELECT statements running
1301 concurrently with INSERT and UPDATE statements. A long-running
1302 SELECT can in certain circumstances block INSERTs and
1303 therefore block mod_log_sql. A workaround is to enable
1304 mod_log_sql for "delayed inserts," which are described as
1305 follows in the MySQL documentation.
1306 </para>
1307 <para>
1308 The DELAYED option for the INSERT statement is a
1309 MySQL-specific option that is very useful if you have clients
1310 that can't wait for the INSERT to complete. This is a common
1311 problem when you use MySQL for logging and you also
1312 periodically run SELECT and UPDATE statements that take a long
1313 time to complete. DELAYED was introduced in MySQL Version
1314 3.22.15. It is a MySQL extension to ANSI SQL92.
1315 </para>
1316 <para>
1317 INSERT DELAYED only works with ISAM and MyISAM tables. Note
1318 that as MyISAM tables supports concurrent SELECT and INSERT,
1319 if there is no free blocks in the middle of the data file, you
1320 very seldom need to use INSERT DELAYED with MyISAM.
1321 </para>
1322 <para>
1323 When you use INSERT DELAYED, the client will get an OK at once
1324 and the row will be inserted when the table is not in use by
1325 any other thread.
1326 </para>
1327 <para>
1328 Another major benefit of using INSERT DELAYED is that inserts
1329 from many clients are bundled together and written in one
1330 block. This is much faster than doing many separate inserts.
1331 </para>
1332 <para>The general disadvantages of delayed inserts are</para>
1333 <orderedlist>
1334 <listitem>
1335 <para>
1336 The queued rows are only stored in memory until they are
1337 inserted into the table. If mysqld dies unexpectedly, any
1338 queued rows that were not written to disk are lost.
1339 </para>
1340 </listitem>
1341 <listitem>
1342 <para>
1343 There is additional overhead for the server to handle a
1344 separate thread for each table on which you use INSERT
1345 DELAYED.
1346 </para>
1347 </listitem>
1348 </orderedlist>
1349 <note role="warning">
1350 <para>
1351 The MySQL documentation concludes, "This means that you
1352 should only use INSERT DELAYED when you are really sure you
1353 need it!" Furthermore, the current state of error return
1354 from a failed INSERT DELAYED seems to be in flux, and may
1355 behave in unpredictable ways between different MySQL
1356 versions. See FAQ entry
1357 <xref linkend="FAQ.DelayedInsert" />
1358 -- you have been warned.
1359 </para>
1360 </note>
1361 <para>
1362 If you are experiencing issues which could be solved by
1363 delayed inserts, then set LogSqlDelayedInserts On in the
1364 <filename>httpd.conf</filename>
1365 . All regular INSERT statements are now INSERT DELAYED, and
1366 you should see no more blocking of the module.
1367 </para>
1368 </section>
1369 </section>
1370 <section id="Sect.ConfigReference">
1371 <title id="Sect.ConfigReference.title">
1372 Configuration Directive Reference
1373 </title>
1374 <para>
1375 It is imperative that you understand which directives are used
1376 only once in the main server config, and which are used inside
1377 VirtualHost stanzas and therefore multiple times within
1378 httpd.conf. The "context" listed with each entry informs you of
1379 this.
1380 </para>
1381 <section tocstyle="fragment">
1382 <title>DataBase Configuration</title>
1383 <variablelist>
1384 <varlistentry>
1385 <term>LogSQLLoginInfo</term>
1386 <listitem>
1387 <cmdsynopsis>
1388 <command>LogSQLLoginInfo</command>
1389 <arg choice="req">
1390 <replaceable>connection URI</replaceable>
1391 </arg>
1392 </cmdsynopsis>
1393 <simpara>
1394 Example: LogSQLLoginInfo
1395 mysql://logwriter:passw0rd@foobar.baz.com/Apache_log
1396 </simpara>
1397 <simpara>Context: main server config</simpara>
1398 <para>
1399 Defines the basic connection URI to connect to the
1400 database with. The format of the connection URI is
1401 </para>
1402 <simpara>
1403 driver://username[:password]@hostname[:port]/database
1404 </simpara>
1405 <variablelist>
1406 <varlistentry>
1407 <term>driver</term>
1408 <listitem>
1409 <simpara>
1410 The database driver to use (mysql, pgsql, etc..)
1411 </simpara>
1412 </listitem>
1413 </varlistentry>
1414 <varlistentry>
1415 <term>username</term>
1416 <listitem>
1417 <simpara>
1418 The database username to login with INSERT
1419 privileges on the logging table defined in
1420 LogSQLtransferLogTable.
1421 </simpara>
1422 </listitem>
1423 </varlistentry>
1424 <varlistentry>
1425 <term>password</term>
1426 <listitem>
1427 <simpara>
1428 The password to use for username, and can be
1429 omitted if there is no password.
1430 </simpara>
1431 </listitem>
1432 </varlistentry>
1433 <varlistentry>
1434 <term>hostname</term>
1435 <listitem>
1436 <simpara>
1437 The hostname or Ip address of the Database
1438 machine, ans is simple "localhost" if the database
1439 lives on the same machine as Apache.
1440 </simpara>
1441 </listitem>
1442 </varlistentry>
1443 <varlistentry>
1444 <term>port</term>
1445 <listitem>
1446 <simpara>
1447 Port on hostname to connect to the Database, if
1448 not specified use the default port for the
1449 database.
1450 </simpara>
1451 </listitem>
1452 </varlistentry>
1453 <varlistentry>
1454 <term>database</term>
1455 <listitem>
1456 <simpara>
1457 The database to connect to on the server.
1458 </simpara>
1459 </listitem>
1460 </varlistentry>
1461 </variablelist>
1462 <note>
1463 <para>
1464 This is defined only once in the
1465 <filename>httpd.conf</filename>
1466 file.
1467 </para>
1468 <para>
1469 This directive Must be defined for logging to be
1470 enabled.
1471 </para>
1472 </note>
1473 </listitem>
1474 </varlistentry>
1475 <varlistentry>
1476 <term>LogSQLDBParam</term>
1477 <listitem>
1478 <cmdsynopsis sepchar=" ">
1479 <command>LogSQLDBParam</command>
1480 <arg choice="req">
1481 <replaceable>parameter-name</replaceable>
1482 </arg>
1483 <arg choice="req">
1484 <replaceable>value</replaceable>
1485 </arg>
1486 </cmdsynopsis>
1487 <simpara>
1488 Example: LogSQLDBParam socketfile
1489 /var/lib/mysql/mysql.socket
1490 </simpara>
1491 <simpara>Context: main server config</simpara>
1492 <para>
1493 This is the new method of specifying Database connection
1494 credentials and settings. This is used to define
1495 database driver specific options. For a list of options
1496 read the documentation for each specific database
1497 driver.
1498 </para>
1499 <table>
1500 <title>MySQL Driver parameters</title>
1501 <tgroup cols="5">
1502 <colspec colname="1" colnum="1" />
1503 <colspec colname="2" colnum="2" />
1504 <colspec colname="3" colnum="3" />
1505 <thead>
1506 <row>
1507 <entry colname="1">Parameter</entry>
1508 <entry colname="2">Meaning</entry>
1509 <entry colname="3">Default</entry>
1510 </row>
1511 </thead>
1512 <tbody>
1513 <row>
1514 <entry colname="1">hostname</entry>
1515 <entry colname="2">MySQL Server hostname</entry>
1516 <entry colname="3">none (use LogSQLLoginInfo to set)</entry>
1517 </row>
1518 <row>
1519 <entry colname="1">username</entry>
1520 <entry colname="2">The username to log in with</entry>
1521 <entry colname="3">none (use LogSQLLoginInfo to set)</entry>
1522 </row>
1523 <row>
1524 <entry colname="1">password</entry>
1525 <entry colname="2">The password to use</entry>
1526 <entry colname="3">none (use LogSQLLoginInfo to set)</entry>
1527 </row>
1528 <row>
1529 <entry colname="1">database</entry>
1530 <entry colname="2">Which database to connect to</entry>
1531 <entry colname="3">none (use LogSQLLoginInfo to set)</entry>
1532 </row>
1533 <row>
1534 <entry colname="1">port</entry>
1535 <entry colname="2">The TCP port to connect to the MySQL server over</entry>
1536 <entry colname="3">3306 (use LogSQLLoginInfo to set)</entry>
1537 </row>
1538 <row>
1539 <entry colname="1">socketfile</entry>
1540 <entry colname="2">The MySQL Unix socket file to use</entry>
1541 <entry colname="3">none</entry>
1542 </row>
1543 <row>
1544 <entry colname="1">tabletype</entry>
1545 <entry colname="2">MySQL Table Engine to use</entry>
1546 <entry colname="3">MySQL server default</entry>
1547 </row>
1548 </tbody>
1549 </tgroup>
1550 </table>
1551 <note>
1552 <para>
1553 Each parameter-name may only be defined once.
1554 </para>
1555 </note>
1556 </listitem>
1557 </varlistentry>
1558 <varlistentry>
1559 <term>LogSQLCreateTables</term>
1560 <listitem>
1561 <cmdsynopsis sepchar=" ">
1562 <command>LogSQLCreateTables</command>
1563 <arg choice="req">flag</arg>
1564 </cmdsynopsis>
1565 <simpara>Example: LogSQLCreateTables On</simpara>
1566 <simpara>Default: Off</simpara>
1567 <simpara>Context: main server config</simpara>
1568 <para>
1569 mod_log_sql has the ability to create its tables
1570 on-the-fly. The advantage to this is convenience: you
1571 don't have to execute any SQL by hand to prepare the
1572 table. This is especially helpful for people with lots
1573 of virtual hosts (who should also see the
1574 LogSQLMassVirtualHosting directive).
1575 </para>
1576 <para>
1577 There is a slight disadvantage: if you wish to activate
1578 this feature, then the userid specified in
1579 LogSQLLoginInfo must have CREATE privileges on the
1580 database. In an absolutely paranoid, locked-down
1581 situation you may only want to grant your mod_log_sql
1582 user INSERT privileges on the database; in that
1583 situation you are unable to take advantage of
1584 LogSQLCreateTables. But most people -- even the very
1585 security-conscious -- will find that granting CREATE on
1586 the logging database is reasonable.
1587 </para>
1588 <note>
1589 <para>
1590 This is defined only once in the
1591 <filename>httpd.conf</filename>
1592 file.
1593 </para>
1594 </note>
1595 </listitem>
1596 </varlistentry>
1597 <varlistentry>
1598 <term>LogSQLForcePreserve</term>
1599 <listitem>
1600 <cmdsynopsis sepchar=" ">
1601 <command>LogSQLForcePreserve</command>
1602 <arg choice="req">flag</arg>
1603 </cmdsynopsis>
1604 <simpara>Example: LogForcePreserve On</simpara>
1605 <simpara>Default: Off</simpara>
1606 <simpara>Context: main server config</simpara>
1607 <para>
1608 You may need to perform debugging on your database and
1609 specifically want mod_log_sql to make no attempts to log
1610 to it. This directive instructs the module to send all
1611 its log entries directly to the preserve file and to
1612 make no database INSERT attempts.
1613 </para>
1614 <para>
1615 This is presumably a directive for temporary use only;
1616 it could be dangerous if you set it and forget it, as
1617 all your entries will simply pile up in the preserve
1618 file.
1619 </para>
1620 <note>
1621 <para>
1622 This is defined only once in the
1623 <filename>httpd.conf</filename>
1624 file.
1625 </para>
1626 </note>
1627 </listitem>
1628 </varlistentry>
1629 <varlistentry>
1630 <term>LogSQLDisablePreserve</term>
1631 <listitem>
1632 <cmdsynopsis>
1633 <command>LogSQLDisablePreserve</command>
1634 <arg choice="req">flag</arg>
1635 </cmdsynopsis>
1636 <simpara>Example: LogDisablePreserve On</simpara>
1637 <simpara>Default: Off</simpara>
1638 <simpara>Context; main server config</simpara>
1639 <para>
1640 This option can be enabled to completely disable the
1641 preserve file fail back. This may be useful for servers
1642 where the file-system is read-only.
1643 </para>
1644 <para>
1645 If the database is not available those log entries will
1646 be lost.
1647 </para>
1648 <note>
1649 <para>
1650 This is defined only once in the
1651 <filename>httpd.conf</filename>
1652 file.
1653 </para>
1654 </note>
1655 </listitem>
1656 </varlistentry>
1657 <varlistentry>
1658 <term>LogSQLMachineID</term>
1659 <listitem>
1660 <cmdsynopsis sepchar=" ">
1661 <command>LogSQLMachineID</command>
1662 <arg choice="req">machineID</arg>
1663 </cmdsynopsis>
1664 <simpara>Example: LogSQLMachineID web01</simpara>
1665 <simpara>Context: main server config</simpara>
1666 <para>
1667 If you have a farm of webservers then you may wish to
1668 know which particular machine made each entry; this is
1669 useful for analyzing your load-balancing methodology.
1670 LogSQLMachineID permits you to distinguish each
1671 machine's entries if you assign each machine its own
1672 LogSQLMachineID: for example, the first webserver gets
1673 ``LogSQLMachineID web01,'' the second gets
1674 ``LogSQLMachineID web02,'' etc.
1675 </para>
1676 <note>
1677 <para>
1678 This is defined only once in the
1679 <filename>httpd.conf</filename>
1680 file.
1681 </para>
1682 </note>
1683 </listitem>
1684 </varlistentry>
1685 <varlistentry>
1686 <term>LogSQlPreserveFile</term>
1687 <listitem>
1688 <cmdsynopsis sepchar=" ">
1689 <command>LogSQLPreserveFile</command>
1690 <arg choice="req">
1691 <replaceable>filename</replaceable>
1692 </arg>
1693 </cmdsynopsis>
1694 <simpara>
1695 Example: LogSQLPreserveFile offline-preserve
1696 </simpara>
1697 <simpara>Default: /tmp/sql-preserve</simpara>
1698 <simpara>Context: virtual host</simpara>
1699 <para>
1700 mod_log_sql writes queries to this local preserve file
1701 in the event that it cannot reach the database, and thus
1702 ensures that your high-availability web frontend does
1703 not lose logs during a temporary database outage. This
1704 could happen for a number of reasons: the database goes
1705 offline, the network breaks, etc. You will not lose
1706 entries since the module has this backup. The file
1707 consists of a series of SQL statements that can be
1708 imported into your database at your convenience;
1709 furthermore, because the SQL queries contain the access
1710 timestamps you do not need to worry about out-of-order
1711 data after the import, which is done in a simple manner:
1712 </para>
1713 <programlisting format="linespecific"># mysql -uadminuser -p mydbname &lt; /tmp/sql-preserve</programlisting>
1714 <para>
1715 If you do not define LogSQLPreserveFile then all virtual
1716 servers will log to the same default preserve file (
1717 <filename>/tmp/sql-preserve</filename>
1718 ). You can redefine this on a virtual-host basis in
1719 order to segregate your preserve files if you desire.
1720 Note that segregation is not usually necessary, as the
1721 SQL statements that are written to the preserve file
1722 already distinguish between different virtual hosts if
1723 you include the 'v' character in your
1724 LogSQLTransferLogFormat directive. It is only necessary
1725 to segregate preserve-files by virualhost if you also
1726 segregate access logs by virtualhost.
1727 </para>
1728 <para>
1729 The module will log to Apache's ErrorLog when it notices
1730 a database outage, and upon database return. You will
1731 therefore know when the preserve file is being used,
1732 although it is your responsibility to import the file.
1733 </para>
1734 <para>
1735 The file does not need to be created in advance. It is
1736 safe to remove or rename the file without interrupting
1737 Apache, as the module closes the filehandle immediately
1738 after completing the write. The file is created with the
1739 user &amp; group ID of the running Apache process (e.g.
1740 'nobody' on many Linux distributions).
1741 </para>
1742 </listitem>
1743 </varlistentry>
1744 </variablelist>
1745 </section>
1746 <section>
1747 <title>Table Names</title>
1748 <variablelist>
1749 <varlistentry>
1750 <term>LogSQLTransferLogTable</term>
1751 <listitem>
1752 <cmdsynopsis sepchar=" ">
1753 <command>LogSQLTransferLogTable</command>
1754 <arg choice="req">
1755 <replaceable>table-name</replaceable>
1756 </arg>
1757 </cmdsynopsis>
1758 <simpara>
1759 Example: LogSQLTransferLogTable access_log_table
1760 </simpara>
1761 <simpara>Context: virtual host</simpara>
1762 <para>
1763 Defines which table is used for logging of Apache's
1764 transfers; this is analogous to Apache's TransferLog
1765 directive. table-name must be a valid table within the
1766 database defined in the LogSQLLoginInfo connection URI.
1767 </para>
1768 <para>
1769 This directive is
1770 <emphasis>not</emphasis>
1771 necessary if you declare LogSQLMassVirtualHosting On,
1772 since that directive activates dynamically-named tables.
1773 If you attempt to use LogSqlTransferlogTable at the same
1774 time a warning will be logged and it will be ignored,
1775 since LogSQLMassVirtualHosting takes priority.
1776 </para>
1777 <note>
1778 <para>
1779 Requires unless LogSQLMassVirtualHosting is set to On
1780 </para>
1781 </note>
1782 </listitem>
1783 </varlistentry>
1784 <varlistentry>
1785 <term>LogSQLCookieLogTable</term>
1786 <listitem>
1787 <cmdsynopsis sepchar=" ">
1788 <command>LogSQLCookieLogTable</command>
1789 <arg choice="req">
1790 <replaceable></replaceable>
1791 table-name
1792 </arg>
1793 </cmdsynopsis>
1794 <simpara>
1795 Example: LogSQLCookieLogTable cookie_log
1796 </simpara>
1797 <simpara>Default: cookies</simpara>
1798 <simpara>Context: virtual host</simpara>
1799 <para>
1800 Defines which table is used for logging of cookies.
1801 Working in conjunction with LogSQLWhichCookies, you can
1802 log many of each request's associated cookies to a
1803 separate table. For meaningful data retrieval the cookie
1804 table is keyed to the access table by the unique request
1805 ID supplied by the standard Apache module mod_unique_id.
1806 </para>
1807 <note>
1808 <para>
1809 You must create the table (see create-tables.sql,
1810 included in the package), or LogSQLCreateTables must
1811 be set to "on".
1812 </para>
1813 </note>
1814 </listitem>
1815 </varlistentry>
1816 <varlistentry>
1817 <term>LogSQLHeadersInLogTable</term>
1818 <listitem>
1819 <cmdsynopsis sepchar=" ">
1820 <command>LogSQLHeadersInLogTable</command>
1821 <arg choice="req">
1822 <replaceable>table-name</replaceable>
1823 </arg>
1824 </cmdsynopsis>
1825 <simpara>
1826 Example: LogSQLHeadersInLogTable headers
1827 </simpara>
1828 <simpara>Default: headers_in</simpara>
1829 <simpara>Context: virtual host</simpara>
1830 <para>
1831 Defines which table is used for logging of inbound
1832 headers. Working in conjunction with
1833 LogSQLWhichHeadersIn, you can log many of each request's
1834 associated headers to a separate table. For meaningful
1835 data retrieval the headers table is keyed to the access
1836 table by the unique request ID supplied by the standard
1837 Apache module mod_unique_id.
1838 </para>
1839 <note>
1840 <para>
1841 Note that you must create the table (see
1842 create-tables.sql, included in the package), or
1843 LogSQLCreateTables must be set to "on".
1844 </para>
1845 </note>
1846 </listitem>
1847 </varlistentry>
1848 <varlistentry>
1849 <term>LogSQLHeadersOutLogTable</term>
1850 <listitem>
1851 <cmdsynopsis sepchar=" ">
1852 <command>LogSQLHeadersOutLogTable</command>
1853 <arg choice="req">
1854 <replaceable>table-name</replaceable>
1855 </arg>
1856 </cmdsynopsis>
1857 <simpara>
1858 Example: LogSQLHeadersOutLogTable headers
1859 </simpara>
1860 <simpara>Default: headers_out</simpara>
1861 <simpara>Context: virtual host</simpara>
1862 <para>
1863 Defines which table is used for logging of outbound
1864 headers. Working in conjunction with
1865 LogSQLWhichHeadersOut, you can log many of each
1866 request's associated headers to a separate table. For
1867 meaningful data retrieval the headers table is keyed to
1868 the access table by the unique request ID supplied by
1869 the standard Apache module mod_unique_id.
1870 </para>
1871 <note>
1872 <para>
1873 Note that you must create the table (see
1874 create-tables.sql, included in the package), or
1875 LogSQLCreateTables must be set to "on".
1876 </para>
1877 </note>
1878 </listitem>
1879 </varlistentry>
1880 <varlistentry>
1881 <term>LogSQLNotesLogTable</term>
1882 <listitem>
1883 <cmdsynopsis sepchar=" ">
1884 <command>LogSQLNotesLogTable</command>
1885 <arg choice="req">
1886 <replaceable>table-name</replaceable>
1887 </arg>
1888 </cmdsynopsis>
1889 <simpara>Example: LogSQLNotesLogTable notes-log</simpara>
1890 <simpara>Default: notes</simpara>
1891 <simpara>Context: virtual_host</simpara>
1892 <para>
1893 Defines which table is used for logging of notes.
1894 Working in conjunction with LogSQLWhichNotes, you can
1895 log many of each request's associated notes to a
1896 separate table. For meaningful data retrieval the notes
1897 table is keyed to the access table by the unique request
1898 ID supplied by the standard Apache module mod_unique_id.
1899 </para>
1900 <note>
1901 <para>
1902 This table must be created (see create-tables.sql
1903 included in the package), or LogSQLCreateTables must
1904 be set to 'On'.
1905 </para>
1906 </note>
1907 </listitem>
1908 </varlistentry>
1909 <varlistentry>
1910 <term>LogSQLMassVirtualHosting</term>
1911 <listitem>
1912 <cmdsynopsis sepchar=" ">
1913 <command>LogSQLMassVirtualHosting</command>
1914 <arg choice="req">flag</arg>
1915 </cmdsynopsis>
1916 <simpara>Example: LogSQLMassVirtualHosting On</simpara>
1917 <simpara>Default: Off</simpara>
1918 <simpara>Context: main server config</simpara>
1919 <para>
1920 If you administer a site hosting many, many virtual
1921 hosts then this option will appeal to you. If you turn
1922 on LogSQLMassVirtualHosting then several things happen:
1923 </para>
1924 <itemizedlist>
1925 <listitem>
1926 <para>
1927 the on-the-fly table creation feature is activated
1928 automatically
1929 </para>
1930 </listitem>
1931 <listitem>
1932 <para>
1933 the transfer log table name is dynamically set from
1934 the virtual host's name after stripping out
1935 SQL-unfriendly characters (example: a virtual host
1936 www.grubbybaby.com gets logged to table
1937 access_www_grubbybaby_com)
1938 </para>
1939 </listitem>
1940 <listitem>
1941 <para>
1942 which, in turn, means that each virtual host logs to
1943 its own segregated table. Because there is no data
1944 shared between virtual servers you can grant your
1945 users access to the tables they need; they will be
1946 unable to view others' data.
1947 </para>
1948 </listitem>
1949 </itemizedlist>
1950 <para>
1951 This is a huge boost in convenience for sites with many
1952 virtual servers. Activating LogSQLMassVirtualHosting
1953 obviates the need to create every virtual server's table
1954 and provides more granular security possibilities.
1955 </para>
1956 <note>
1957 <para>
1958 This is defined only once in the
1959 <filename>httpd.conf</filename>
1960 file.
1961 </para>
1962 </note>
1963 </listitem>
1964 </varlistentry>
1965 </variablelist>
1966 </section>
1967 <section>
1968 <title>Configuring What Is logged</title>
1969 <variablelist>
1970 <varlistentry id="Conf.LogSQLTransferLogFormat">
1971 <term>LogSQLTransferLogFormat</term>
1972 <listitem>
1973 <cmdsynopsis sepchar=" ">
1974 <command>LogSQLTransferLogFormat</command>
1975 <arg choice="req">
1976 <replaceable>format-string</replaceable>
1977 </arg>
1978 </cmdsynopsis>
1979 <simpara>Example: LogSQLTransferLogFormat huSUTv</simpara>
1980 <simpara>Default: AbHhmRSsTUuv</simpara>
1981 <simpara>Context: virtual host</simpara>
1982 <para>
1983 Each character in the format-string defines an attribute
1984 of the request that you wish to log. The default logs
1985 the information required to create Combined Log Format
1986 logs, plus several extras. Here is the full list of
1987 allowable keys, which sometimes resemble their Apache
1988 counterparts, but do not always:
1989 </para>
1990 <table>
1991 <title>Core LogFormat parameters</title>
1992 <tgroup cols="5">
1993 <colspec colname="1" colnum="1" />
1994 <colspec colname="2" colnum="2" />
1995 <colspec colname="3" colnum="3" />
1996 <colspec colname="4" colnum="4" />
1997 <colspec colname="5" colnum="5" />
1998 <thead>
1999 <row>
2000 <entry colname="1">Symbol</entry>
2001 <entry colname="2">Meaning</entry>
2002 <entry colname="3">DB Field</entry>
2003 <entry colname="4">Data Type</entry>
2004 <entry colname="5">Example</entry>
2005 </row>
2006 </thead>
2007 <tbody>
2008 <row>
2009 <entry colname="1">A</entry>
2010 <entry colname="2">User Agent</entry>
2011 <entry colname="3">agent</entry>
2012 <entry colname="4">varchar(255)</entry>
2013 <entry colname="5">
2014 Mozilla/4.0 (compat; MSIE 6.0; Windows)
2015 </entry>
2016 </row>
2017 <row>
2018 <entry colname="1">a</entry>
2019 <entry colname="2">CGi request arguments</entry>
2020 <entry colname="3">request_args</entry>
2021 <entry colname="4">varchar(255)</entry>
2022 <entry colname="5">
2023 user=Smith&amp;cart=1231&amp;item=532
2024 </entry>
2025 </row>
2026 <row>
2027 <entry colname="1">b</entry>
2028 <entry colname="2">Bytes transfered</entry>
2029 <entry colname="3">bytes_sent</entry>
2030 <entry colname="4">int unsigned</entry>
2031 <entry colname="5">32561</entry>
2032 </row>
2033 <row>
2034 <entry colname="1">
2035 c
2036 <xref linkend="Foot.LogCookie" xrefstyle="footer" />
2037 </entry>
2038 <entry colname="2">Text of cookie</entry>
2039 <entry colname="3">cookie</entry>
2040 <entry colname="4">varchar(255)</entry>
2041 <entry colname="5">
2042 Apache=sdyn.fooonline.net 1300102700823
2043 </entry>
2044 </row>
2045 <row>
2046 <entry>f</entry>
2047 <entry>Local filename requested</entry>
2048 <entry>request_file</entry>
2049 <entry>varchar(255)</entry>
2050 <entry>/var/www/html/books-cycroad.html</entry>
2051 </row>
2052 <row>
2053 <entry>H</entry>
2054 <entry>HTTP request_protocol</entry>
2055 <entry>request_protocol</entry>
2056 <entry>varchar(10)</entry>
2057 <entry>HTTP/1.1</entry>
2058 </row>
2059 <row>
2060 <entry>h</entry>
2061 <entry>Name of remote host</entry>
2062 <entry>remote_host</entry>
2063 <entry>varchar(50)</entry>
2064 <entry>blah.foobar.com</entry>
2065 </row>
2066 <row>
2067 <entry>I</entry>
2068 <entry>Request ID (from modd_unique_id)</entry>
2069 <entry>id</entry>
2070 <entry>char(19)</entry>
2071 <entry>POlFcUBRH30AAALdBG8</entry>
2072 </row>
2073 <row>
2074 <entry>l</entry>
2075 <entry>Ident user info</entry>
2076 <entry>remote_logname</entry>
2077 <entry>varcgar(50)</entry>
2078 <entry>bobby</entry>
2079 </row>
2080 <row>
2081 <entry>M</entry>
2082 <entry>
2083 Machine ID
2084 <xref linkend="Foot.MachineID" xrefstyle="footer" />
2085 </entry>
2086 <entry>machine_id</entry>
2087 <entry>varchar(25)</entry>
2088 <entry>web01</entry>
2089 </row>
2090 <row>
2091 <entry>m</entry>
2092 <entry>HTTP request method</entry>
2093 <entry>request_method</entry>
2094 <entry>varchar(10)</entry>
2095 <entry>GET</entry>
2096 </row>
2097 <row>
2098 <entry>P</entry>
2099 <entry>httpd cchild PID</entry>
2100 <entry>child_pid</entry>
2101 <entry>smallint unsigned</entry>
2102 <entry>3215</entry>
2103 </row>
2104 <row>
2105 <entry>p</entry>
2106 <entry>http port</entry>
2107 <entry>server_port</entry>
2108 <entry>smallint unsigned</entry>
2109 <entry>80</entry>
2110 </row>
2111 <row>
2112 <entry>R</entry>
2113 <entry>Referer</entry>
2114 <entry>referer</entry>
2115 <entry>varchar(255)</entry>
2116 <entry>
2117 http://www.biglinks4u.com/linkpage.html
2118 </entry>
2119 </row>
2120 <row>
2121 <entry>r</entry>
2122 <entry>Request in full form</entry>
2123 <entry>request_line</entry>
2124 <entry>varchar(255)</entry>
2125 <entry>GET /books-cycroad.html HTTP/1.1</entry>
2126 </row>
2127 <row>
2128 <entry>S</entry>
2129 <entry>
2130 Time of request in UNIX time_t format
2131 </entry>
2132 <entry>time_stamp</entry>
2133 <entry>int unsigned</entry>
2134 <entry>1005598029</entry>
2135 </row>
2136 <row>
2137 <entry>s</entry>
2138 <entry>HTTP Response Code Status</entry>
2139 <entry>status</entry>
2140 <entry>smallint</entry>
2141 <entry>200</entry>
2142 </row>
2143 <row>
2144 <entry>T</entry>
2145 <entry>Seconds to service request</entry>
2146 <entry>request_duration</entry>
2147 <entry>smallint unsigned</entry>
2148 <entry>2</entry>
2149 </row>
2150 <row>
2151 <entry>t</entry>
2152 <entry>Time of request in human format</entry>
2153 <entry>request_time</entry>
2154 <entry>char(28)</entry>
2155 <entry>[02/Dec/2001:15:01:26 -0800]</entry>
2156 </row>
2157 <row>
2158 <entry>U</entry>
2159 <entry>Request in simple form</entry>
2160 <entry>request_uri</entry>
2161 <entry>varchar(255)</entry>
2162 <entry>/books-cycroad.html</entry>
2163 </row>
2164 <row>
2165 <entry>u</entry>
2166 <entry>User info from HTTP auth</entry>
2167 <entry>remote_user</entry>
2168 <entry>varchar(50)</entry>
2169 <entry>bobby</entry>
2170 </row>
2171 <row>
2172 <entry>v</entry>
2173 <entry>Virtual host servicing the request</entry>
2174 <entry>virtual_host</entry>
2175 <entry>varchar(255)</entry>
2176 <entry>www.foobar.com</entry>
2177 </row>
2178 <row>
2179 <entry>V</entry>
2180 <entry>
2181 requested Virtual host name (mass
2182 virtualhosting)
2183 </entry>
2184 <entry>virtual_host</entry>
2185 <entry>varchar(255)</entry>
2186 <entry>www.foobar.org</entry>
2187 </row>
2188 </tbody>
2189 </tgroup>
2190 </table>
2191 <note>
2192 <simpara id="Foot.LogCookie">
2193 [1] You must also specify LogSQLWhichCookie for this
2194 to take effect.
2195 </simpara>
2196 <simpara id="Foot.MachineID">
2197 [2] You must also specify LogSQLmachineID for this to
2198 take effect.
2199 </simpara>
2200 </note>
2201 <table>
2202 <title>SSL LogFormat Parameters</title>
2203 <tgroup cols="5">
2204 <colspec colname="1" colnum="1" />
2205 <colspec colname="2" colnum="2" />
2206 <colspec colname="3" colnum="3" />
2207 <colspec colname="4" colnum="4" />
2208 <colspec colname="5" colnum="5" />
2209 <thead>
2210 <row>
2211 <entry colname="1">Symbol</entry>
2212 <entry colname="2">Meaning</entry>
2213 <entry colname="3">DB Field</entry>
2214 <entry colname="4">Data Type</entry>
2215 <entry colname="5">Example</entry>
2216 </row>
2217 </thead>
2218 <tbody>
2219 <row>
2220 <entry colname="1">z</entry>
2221 <entry colname="2">SSL cipher used</entry>
2222 <entry colname="3">ssl_cipher</entry>
2223 <entry colname="4">varchar(25)</entry>
2224 <entry colname="5">RC4-MD5</entry>
2225 </row>
2226 <row>
2227 <entry colname="1">q</entry>
2228 <entry colname="2">
2229 Keysize of the SSL connection
2230 </entry>
2231 <entry colname="3">ssl_keysize</entry>
2232 <entry colname="4">smallint unsigned</entry>
2233 <entry colname="5">56</entry>
2234 </row>
2235 <row>
2236 <entry colname="1">Q</entry>
2237 <entry colname="2">
2238 maximum keysize supported
2239 </entry>
2240 <entry colname="3">ssl_maxkeysize</entry>
2241 <entry colname="4">smallint unsigned</entry>
2242 <entry colname="5">128</entry>
2243 </row>
2244 </tbody>
2245 </tgroup>
2246 </table>
2247 <table>
2248 <title>LogIO LogFormat Parameters</title>
2249 <tgroup cols="5">
2250 <colspec colname="1" colnum="1" />
2251 <colspec colname="2" colnum="2" />
2252 <colspec colname="3" colnum="3" />
2253 <colspec colname="4" colnum="4" />
2254 <colspec colname="5" colnum="5" />
2255 <thead>
2256 <row>
2257 <entry colname="1">Symbol</entry>
2258 <entry colname="2">Meaning</entry>
2259 <entry colname="3">DB Field</entry>
2260 <entry colname="4">Data Type</entry>
2261 <entry colname="5">Example</entry>
2262 </row>
2263 </thead>
2264 <tbody>
2265 <row>
2266 <entry colname="1">i</entry>
2267 <entry colname="2">Number of actual Bytes transfered in with the request</entry>
2268 <entry colname="3">bytes_in</entry>
2269 <entry colname="4">int unsigned</entry>
2270 <entry colname="5">505</entry>
2271 </row>
2272 <row>
2273 <entry colname="1">o</entry>
2274 <entry colname="2">Number of actual Bytes transfered out with the request</entry>
2275 <entry colname="3">bytes_out</entry>
2276 <entry colname="4">int unsigned</entry>
2277 <entry colname="5">4168</entry>
2278 </row>
2279 </tbody>
2280 </tgroup>
2281 </table>
2282 </listitem>
2283 </varlistentry>
2284 <varlistentry>
2285 <term>LogSQLRemhostIgnore</term>
2286 <listitem>
2287 <cmdsynopsis sepchar=" ">
2288 <command>LogSQLRemhostIgnore</command>
2289 <arg choice="req" rep="repeat">
2290 <replaceable>hostname</replaceable>
2291 </arg>
2292 </cmdsynopsis>
2293 <simpara>
2294 Example: LogSQLRemhostIgnore localnet.com
2295 </simpara>
2296 <simpara>Context: virtual host</simpara>
2297 <para>
2298 Lists a series of smortrings that, if present in the
2299 REMOTE_HOST, will cause that request to
2300 <emphasis>not</emphasis>
2301 be logged. This directive is useful for cutting down on
2302 log clutter when you are certain that you want to ignore
2303 requests from certain hosts, such as your own internal
2304 network machines. See section
2305 <xref endterm="Sect.Ignore.title" linkend="Sect.Ignore" />
2306 for some tips for using this directive.
2307 </para>
2308 <para>
2309 Each string may contain a + or - prefix in a
2310 &lt;VirtualHost&gt; context and will cause those strings
2311 to be added (+) or removed (-) from the global
2312 configuration. Otherwise the global is completely
2313 ignored and overridden if defined in a
2314 &lt;VirtualHost&gt;
2315 </para>
2316 <para>
2317 Each string is separated by a space, and no regular
2318 expressions or globbing are allowed. Each string is
2319 evaluated as a substring of the REMOTE_HOST using
2320 strstr(). The comparison is case sensitive.
2321 </para>
2322 </listitem>
2323 </varlistentry>
2324 <varlistentry>
2325 <term>LogSQLRequestAccept</term>
2326 <listitem>
2327 <cmdsynopsis sepchar=" ">
2328 <command>LogSQLRequestAccept</command>
2329 <arg choice="req" rep="repeat">
2330 <replaceable>substring</replaceable>
2331 </arg>
2332 </cmdsynopsis>
2333 <simpara>
2334 Example: LogSQLRequestAccept .html .php .jpg
2335 </simpara>
2336 <simpara>
2337 Default: if not specified, all requests are 'accepted'
2338 </simpara>
2339 <simpara>Context: virtual host</simpara>
2340 <para>
2341 Lists a series of strings that, if present in the URI,
2342 will permit that request to be considered for logging
2343 (depending on additional filtering by the "ignore"
2344 directives). Any request that fails to match one of the
2345 LogSQLRequestAccept entries will be discarded.
2346 </para>
2347 <para>
2348 Each string may contain a + or - prefix in a
2349 &lt;VirtualHost&gt; context and will cause those strings
2350 to be added (+) or removed (-) from the global
2351 configuration. Otherwise the global is completely
2352 ignored and overridden if defined in a
2353 &lt;VirtualHost&gt;
2354 </para>
2355 <para>
2356 This directive is useful for cutting down on log clutter
2357 when you are certain that you only want to log certain
2358 kinds of requests, and just blanket-ignore everything
2359 else. See section
2360 <xref endterm="Sect.Ignore.title" linkend="Sect.Ignore" />
2361 for some tips for using this directive.
2362 </para>
2363 <para>
2364 Each string is separated by a space, and no regular
2365 expressions or globbing are allowed. Each string is
2366 evaluated as a substring of the URI using strstr(). The
2367 comparison is case sensitive.
2368 </para>
2369 <para>
2370 This directive is completely optional. It is more
2371 general than LogSQLRequestIgnore and is evaluated before
2372 LogSQLRequestIgnore . If this directive is not used,
2373 <emphasis>all</emphasis>
2374 requests are accepted and passed on to the other
2375 filtering directives. Therefore, only use this directive
2376 if you have a specific reason to do so.
2377 </para>
2378 </listitem>
2379 </varlistentry>
2380 <varlistentry>
2381 <term>LogSQLRequestIgnore</term>
2382 <listitem>
2383 <cmdsynopsis sepchar=" ">
2384 <command>LogSQLRequestIgnore</command>
2385 <arg choice="req" rep="repeat">
2386 <replaceable>substring</replaceable>
2387 </arg>
2388 </cmdsynopsis>
2389 <simpara>
2390 Example: LogSQLRequestIgnore root.exe cmd.exe
2391 default.ida favicon.ico
2392 </simpara>
2393 <simpara>Context: virtual host</simpara>
2394 <para>
2395 Lists a series of strings that, if present in the URI,
2396 will cause that request to
2397 <emphasis>NOT</emphasis>
2398 be logged. This directive is useful for cutting down on
2399 log clutter when you are certain that you want to ignore
2400 requests for certain objects. See section
2401 <xref endterm="Sect.Ignore.title" linkend="Sect.Ignore" />
2402 for some tips for using this directive.
2403 </para>
2404 <para>
2405 Each string may contain a + or - prefix in a
2406 &lt;VirtualHost&gt; context and will cause those strings
2407 to be added (+) or removed (-) from the global
2408 configuration. Otherwise the global is completely
2409 ignored and overridden if defined in a
2410 &lt;VirtualHost&gt;
2411 </para>
2412 <para>
2413 Each string is separated by a space, and no regular
2414 expressions or globbing are allowed. Each string is
2415 evaluated as a substring of the URI using strstr(). The
2416 comparison is case sensitive.
2417 </para>
2418 </listitem>
2419 </varlistentry>
2420 <varlistentry>
2421 <term>LogSQLWhichCookie</term>
2422 <listitem>
2423 <cmdsynopsis sepchar=" ">
2424 <command>LogSQLWhichCookie</command>
2425 <arg choice="req">
2426 <replaceable>cookiename</replaceable>
2427 </arg>
2428 </cmdsynopsis>
2429 <simpara>Example; LogSQLWhichCookie Clicks</simpara>
2430 <simpara>Context: virtual host</simpara>
2431 <para>
2432 In HTTP, cookies have names to distinguish them from
2433 each other. Using mod_usertrack, for example, you can
2434 give your user-tracking cookies a name with the
2435 CookieName directive.
2436 </para>
2437 <para>
2438 mod_log_sql allows you to log cookie information.
2439 LogSQL_WhichCookie tells mod_log_sql which cookie to
2440 log. This is necessary because you will usually be
2441 setting and receiving more than one cookie from a
2442 client.
2443 </para>
2444 <note>
2445 <para>
2446 You must include a 'c' character in
2447 LogSQLTransferLogFormat for this directive to take
2448 effect.
2449 </para>
2450 <para>
2451 although this was origintally intended for people
2452 using mod_usertrack to create user-tracking cookies,
2453 you are not restricted in any way. You can choose
2454 which cookie you wish to log to the database - any
2455 cookie at all - and it does not necessarily have to
2456 have anything to do with mod_usertrack.
2457 </para>
2458 </note>
2459 </listitem>
2460 </varlistentry>
2461 <varlistentry>
2462 <term>LogSQLWhichCookies</term>
2463 <listitem>
2464 <cmdsynopsis sepchar=" ">
2465 <command>LogSQLWhichCookies</command>
2466 <arg choice="req" rep="repeat">
2467 <replaceable>cookie-name</replaceable>
2468 </arg>
2469 </cmdsynopsis>
2470 <simpara>
2471 Example: logSQLWhichCookies userlogin cookie1 cookie2
2472 </simpara>
2473 <simpara>Context: virtual host</simpara>
2474 <para>
2475 Defines the list of cookies you would like logged. This
2476 works in conjunction with LogSQLCookieLogTable. This
2477 directive does
2478 <emphasis>not</emphasis>
2479 require any additional characters to be added to the
2480 LogSQLTransferLogFormat string. The feature is activated
2481 simply by including this directive, upon which you will
2482 begin populating the separate cookie table with data.
2483 </para>
2484 <para>
2485 Each string may contain a + or - prefix in a
2486 &lt;VirtualHost&gt; context and will cause those strings
2487 to be added (+) or removed (-) from the global
2488 configuration. Otherwise the global is completely
2489 ignored and overridden if defined in a
2490 &lt;VirtualHost&gt;
2491 </para>
2492 <note>
2493 <para>
2494 The table must be created (see create-tables.sql,
2495 included in the package), or LogSQLCreateTables must
2496 be set to 'On'.
2497 </para>
2498 </note>
2499 </listitem>
2500 </varlistentry>
2501 <varlistentry>
2502 <term>LogSQLWhichHeadersIn</term>
2503 <listitem>
2504 <cmdsynopsis sepchar=" ">
2505 <command>LogSQLWhichHeadersIn</command>
2506 <arg choice="req" rep="repeat">
2507 <replaceable>header-name</replaceable>
2508 </arg>
2509 </cmdsynopsis>
2510 <simpara>
2511 Example: LogSQLWhichHeadersIn User-Agent Accept-Encoding
2512 Host
2513 </simpara>
2514 <simpara>Context: virtual host</simpara>
2515 <para>
2516 Defines the list of inbound headers you would like
2517 logged. This works in conjunction with
2518 LogSQLHeadersInLogTable. This directive does not require
2519 any additional characters to be added to the
2520 LogSQLTransferLogFormat string. The feature is activated
2521 simply by including this directive, upon which you will
2522 begin populating the separate inbound-headers table with
2523 data.
2524 </para>
2525 <para>
2526 Each string may contain a + or - prefix in a
2527 &lt;VirtualHost&gt; context and will cause those strings
2528 to be added (+) or removed (-) from the global
2529 configuration. Otherwise the global is completely
2530 ignored and overridden if defined in a
2531 &lt;VirtualHost&gt;
2532 </para>
2533 <note>
2534 <para>
2535 The table must be created (see create-tables.sql,
2536 included in the package), or LogSQLCreateTables must
2537 be set to 'On'.
2538 </para>
2539 </note>
2540 </listitem>
2541 </varlistentry>
2542 <varlistentry>
2543 <term>LogSQLWhichHeadersOut</term>
2544 <listitem>
2545 <cmdsynopsis sepchar=" ">
2546 <command>LogSQLWhichHeadersOut</command>
2547 <arg choice="req" rep="repeat">
2548 <replaceable>header-name</replaceable>
2549 </arg>
2550 </cmdsynopsis>
2551 <simpara>
2552 Example: LogSQLWhichHeadersOut Expires Content-Type
2553 Cache-Control
2554 </simpara>
2555 <simpara>Context: virtual host</simpara>
2556 <para>
2557 Defines the list of outbound headers you would like
2558 logged. This works in conjunction with
2559 LogSQLHeadersOutLogTable. This directive does not
2560 require any additional characters to be added to the
2561 LogSQLTransferLogFormat string. The feature is activated
2562 simply by including this directive, upon which you will
2563 begin populating the separate outbound-headers table
2564 with data.
2565 </para>
2566 <para>
2567 Each string may contain a + or - prefix in a
2568 &lt;VirtualHost&gt; context and will cause those strings
2569 to be added (+) or removed (-) from the global
2570 configuration. Otherwise the global is completely
2571 ignored and overridden if defined in a
2572 &lt;VirtualHost&gt;
2573 </para>
2574 <note>
2575 <para>
2576 The table must be created (see create-tables.sql,
2577 included in the package), or LogSQLCreateTables must
2578 be set to 'On'.
2579 </para>
2580 </note>
2581 </listitem>
2582 </varlistentry>
2583 <varlistentry>
2584 <term>LogSQLWhichNotes</term>
2585 <listitem>
2586 <cmdsynopsis sepchar=" ">
2587 <command>LogSQLWhichNotes</command>
2588 <arg choice="req" rep="repeat">
2589 <replaceable>note-name</replaceable>
2590 </arg>
2591 </cmdsynopsis>
2592 <simpara>
2593 Example: LogSQLWhichNotes mod_gzip_result
2594 mod_gzip_ompression_ratio
2595 </simpara>
2596 <simpara>Context: virtual host</simpara>
2597 <para>
2598 Defines the list of notes you would like logged. This
2599 works in conjunction with LogSQLNotesLogTable. This
2600 directive does not require any additional characters to
2601 be added to the LogSQLTransferLogFormat string. The
2602 feature is activated simply by including this directive,
2603 upon which you will begin populating the separate notes
2604 table with data.
2605 </para>
2606 <para>
2607 Each string may contain a + or - prefix in a
2608 &lt;VirtualHost&gt; context and will cause those strings
2609 to be added (+) or removed (-) from the global
2610 configuration. Otherwise the global is completely
2611 ignored and overridden if defined in a
2612 &lt;VirtualHost&gt;
2613 </para>
2614 <note>
2615 <para>
2616 The table must be created (see create-tables.sql,
2617 included in the package), or LogSQLCreateTables must
2618 be set to 'On'.
2619 </para>
2620 </note>
2621 </listitem>
2622 </varlistentry>
2623 </variablelist>
2624 </section>
2625 <section>
2626 <title>Deprecated Commands</title>
2627 <variablelist>
2628 <varlistentry>
2629 <term>LogSQLSocketFile [Deprecated]</term>
2630 <listitem>
2631 <cmdsynopsis sepchar=" ">
2632 <command>LogSQLSocketFile</command>
2633 <arg choice="req">
2634 <replaceable>filename</replaceable>
2635 </arg>
2636 </cmdsynopsis>
2637 <simpara>
2638 Example: LogSQLSocketFile /tmp/mysql.sock
2639 </simpara>
2640 <simpara>Default: (database specific)</simpara>
2641 <simpara>
2642 Default (MySQL): /var/lib/mysql/mysql.sock
2643 </simpara>
2644 <simpara>Context: main server config</simpara>
2645 <para>
2646 At Apache runtime you can specify the MySQL socket file
2647 to use. Set this once in your main server config to
2648 override the default value. This value is irrelevant if
2649 your database resides on a separate machine.
2650 </para>
2651 <para>
2652 mod_log_sql will automatically employ the socket for db
2653 communications if the database resides on the local
2654 host. If the db resides on a separate host the module
2655 will automatically use TCP/IP. This is a function of the
2656 MySQL API and is not user-configurable.
2657 </para>
2658 <note>
2659 <para>
2660 This directive is deprecated in favor of LogSQLDBParam
2661 socketfile [socketfilename]
2662 </para>
2663 <para>
2664 This is defined only once in the
2665 <filename>httpd.conf</filename>
2666 file.
2667 </para>
2668 </note>
2669 </listitem>
2670 </varlistentry>
2671 <varlistentry>
2672 <term>LogSQLTCPPort [Deprecated]</term>
2673 <listitem>
2674 <cmdsynopsis sepchar=" ">
2675 <command>LogSQLTCPPort</command>
2676 <arg choice="req">
2677 <replaceable>port-number</replaceable>
2678 </arg>
2679 </cmdsynopsis>
2680 <simpara>Example: LogSQLTCPPort 3309</simpara>
2681 <simpara>Default: (database specific)</simpara>
2682 <simpara>Default (MySQL): 3306</simpara>
2683 <simpara>Context: main server config</simpara>
2684 <para>
2685 Your database may listen on a different port than the
2686 default. If so, use this directive to instruct the
2687 module which port to use. This directive only applies if
2688 the database is on a different machine connected via
2689 TCP/IP.
2690 </para>
2691 <note>
2692 <para>
2693 This directive is deprecated in favor of LogSQLDBParam
2694 tcpport [port-number]
2695 </para>
2696 <para>
2697 This is defined only once in the
2698 <filename>httpd.conf</filename>
2699 file.
2700 </para>
2701 </note>
2702 </listitem>
2703 </varlistentry>
2704 <varlistentry>
2705 <term>LogSQLDatabase [Deprecated]</term>
2706 <listitem>
2707 <cmdsynopsis sepchar=" ">
2708 <command>LogSQLDatabase</command>
2709 <arg choice="req">
2710 <replaceable>database</replaceable>
2711 </arg>
2712 </cmdsynopsis>
2713 <simpara>Example: LogSQLDatabase loggingdb</simpara>
2714 <simpara>Context: main server config</simpara>
2715 <para>
2716 Defines the database that is used for logging.
2717 "database" must be a valid db on the MySQL host defined
2718 in LogSQLLoginInfo
2719 </para>
2720 <note>
2721 <para>
2722 This directive is deprecated in favor of the URI form
2723 of LogSQLLoginInfo.
2724 </para>
2725 <para>
2726 This is defined only once in the
2727 <filename>httpd.conf</filename>
2728 file.
2729 </para>
2730 </note>
2731 </listitem>
2732 </varlistentry>
2733 </variablelist>
2734 </section>
2735 </section>
2736 </section>
2737 <section id="Sect.FAQ">
2738 <title>FAQ</title>
2739 <qandaset>
2740 <qandadiv>
2741 <title>General module questions</title>
2742 <qandaentry id="FAQ.WhyLogToSQL">
2743 <question>
2744 <para>Why log to an SQL database?</para>
2745 </question>
2746 <answer>
2747 <para>
2748 To begin with, let's get it out of the way: logging to a
2749 database is not a panacea. But while there are
2750 complexities with this solution, the benefit can be
2751 substantial for certain classes of administrator or people
2752 with advanced requirements:
2753 </para>
2754 <itemizedlist>
2755 <listitem>
2756 <para>
2757 Chores like log rotation go away, as you can DELETE
2758 records from the SQL database once they are no longer
2759 useful. For example, the excellent and popular
2760 log-analysis tool Webalizer (http://www.webalizer.com)
2761 does not need historic logs after it has processed
2762 them, enabling you to delete older logs.
2763 </para>
2764 </listitem>
2765 <listitem>
2766 <para>
2767 People with clusters of web servers (for high
2768 availability) will benefit the most - all their
2769 webservers can log to a single SQL database. This
2770 obviates the need to collate/interleave the many
2771 separate logfiles, which can be / highly/ problematic.
2772 </para>
2773 </listitem>
2774 <listitem>
2775 <para>
2776 People acquainted with the power of SQL SELECT
2777 statements will know the flexibility of the extraction
2778 possibilities at their fingertips.
2779 </para>
2780 </listitem>
2781 </itemizedlist>
2782 <para>
2783 For example, do you want to see all your 404's? Do this:
2784 </para>
2785 <programlisting>SELECT remote_host, status, request_uri, bytes_sent, from_unixtime(time_stamp)
2786FROM acc_log_tbl WHERE status=404 ORDER BY time_stamp;</programlisting>
2787 <table>
2788 <title></title>
2789 <tgroup cols="5">
2790 <colspec colname="1" />
2791 <colspec colname="2" />
2792 <colspec colname="3" />
2793 <colspec colname="4" />
2794 <colspec colname="5" />
2795 <thead>
2796 <row>
2797 <entry colname="1">remote_host</entry>
2798 <entry colname="2">status</entry>
2799 <entry colname="3">request_uri</entry>
2800 <entry colname="4">bytes_sent</entry>
2801 <entry colname="5">from_unixtime(time_stamp)</entry>
2802 </row>
2803 </thead>
2804 <tbody>
2805 <row>
2806 <entry colname="1">marge.mmm.co.uk</entry>
2807 <entry colname="2">404</entry>
2808 <entry colname="3">/favicon.ico</entry>
2809 <entry colname="4">321</entry>
2810 <entry colname="5">2001-11-20 02:30:56</entry>
2811 </row>
2812 <row>
2813 <entry colname="1">62.180.239.251</entry>
2814 <entry colname="2">404</entry>
2815 <entry colname="3">/favicon.ico</entry>
2816 <entry colname="4">333</entry>
2817 <entry colname="5">2001-11-20 02:45:25</entry>
2818 </row>
2819 <row>
2820 <entry colname="1">212.234.12.66</entry>
2821 <entry colname="2">404</entry>
2822 <entry colname="3">/favicon.ico</entry>
2823 <entry colname="4">321</entry>
2824 <entry colname="5">2001-11-20 03:01:00</entry>
2825 </row>
2826 <row>
2827 <entry colname="1">212.210.78.254</entry>
2828 <entry colname="2">404</entry>
2829 <entry colname="3">/favicon.ico</entry>
2830 <entry colname="4">333</entry>
2831 <entry colname="5">2001-11-20 03:26:05</entry>
2832 </row>
2833 </tbody>
2834 </tgroup>
2835 </table>
2836 <para>
2837 Or do you want to see how many bytes you've sent within a
2838 certain directory or site? Do this:
2839 </para>
2840 <programlisting>SELECT request_uri,sum(bytes_sent) AS bytes, count(request_uri) AS howmany
2841FROM acc_log_tbl
2842WHERE request_uri LIKE '%mod_log_sql%'
2843GROUP BY request_uri ORDER BY howmany DESC;</programlisting>
2844 <table>
2845 <title></title>
2846 <tgroup cols="3">
2847 <colspec colname="1" />
2848 <colspec colname="2" />
2849 <colspec colname="3" />
2850 <thead>
2851 <row>
2852 <entry colname="1">request_uri</entry>
2853 <entry colname="2">bytes</entry>
2854 <entry colname="3">howmany</entry>
2855 </row>
2856 </thead>
2857 <tbody>
2858 <row>
2859 <entry colname="1">/mod_log_sql/style_1.css</entry>
2860 <entry colname="2">157396</entry>
2861 <entry colname="3">1288</entry>
2862 </row>
2863 <row>
2864 <entry colname="1">/mod_log_sql/</entry>
2865 <entry colname="2">2514337</entry>
2866 <entry colname="3">801</entry>
2867 </row>
2868 <row>
2869 <entry colname="1">
2870 /mod_log_sql/mod_log_sql.tar.gz
2871 </entry>
2872 <entry colname="2">9769312</entry>
2873 <entry colname="3">456</entry>
2874 </row>
2875 <row>
2876 <entry colname="1">/mod_log_sql/faq.html</entry>
2877 <entry colname="2">5038728</entry>
2878 <entry colname="3">436</entry>
2879 </row>
2880 </tbody>
2881 </tgroup>
2882 </table>
2883 <para>
2884 Or maybe you want to see who's linking to you? Do this:
2885 </para>
2886 <programlisting>SELECT count(referer) AS num,referer
2887FROM acc_log_tbl
2888WHERE request_uri='/mod_log_sql/'
2889GROUP BY referer ORDER BY num DESC;</programlisting>
2890 <table>
2891 <title></title>
2892 <tgroup cols="2">
2893 <colspec colname="1" />
2894 <colspec colname="2" />
2895 <thead>
2896 <row>
2897 <entry colname="1">num</entry>
2898 <entry colname="2">referer</entry>
2899 </row>
2900 </thead>
2901 <tbody>
2902 <row>
2903 <entry colname="1">271</entry>
2904 <entry colname="2">
2905 http://freshmeat.net/projects/mod_log_sql/
2906 </entry>
2907 </row>
2908 <row>
2909 <entry colname="1">96</entry>
2910 <entry colname="2">
2911 http://modules.apache.org/search?id=339
2912 </entry>
2913 </row>
2914 <row>
2915 <entry colname="1">48</entry>
2916 <entry colname="2">http://freshmeat.net/</entry>
2917 </row>
2918 <row>
2919 <entry colname="1">8</entry>
2920 <entry colname="2">http://freshmeat.net</entry>
2921 </row>
2922 </tbody>
2923 </tgroup>
2924 </table>
2925 <para>
2926 As you can see, there are myriad possibilities that can be
2927 constructed with the wonderful SQL SELECT statement.
2928 Logging to an SQL database can be really quite useful!
2929 </para>
2930 </answer>
2931 </qandaentry>
2932 <qandaentry>
2933 <question>
2934 <para>Why use MySQL? Are there alternatives?</para>
2935 </question>
2936 <answer>
2937 <para>
2938 MySQL is a robust, free, and very powerful
2939 production-quality database engine. It is well supported
2940 and comes with detailed documentation. Many 3rd-party
2941 software pacakges (e.g. Slashcode, the engine that powers
2942 Slashdot) run exclusively with MySQL. In other words, you
2943 will belong to a very robust and well-supported community
2944 by choosing MySQL.
2945 </para>
2946 <para>
2947 That being said, there are alternatives. PostgreSQL is
2948 probably MySQL's leading "competitor" in the free database
2949 world. There is also an excellent module available for
2950 Apache to permit logging to a PostgreSQL database, called
2951 <ulink url="http://www.digitalstratum.com/pglogd/">
2952 pgLOGd
2953 </ulink>
2954 </para>
2955 </answer>
2956 <answer>
2957 <note>
2958 <para>
2959 Currently a database abstraction system is in the works
2960 to allow any database to be used with mod_log_sql.
2961 </para>
2962 </note>
2963 </answer>
2964 </qandaentry>
2965 <qandaentry>
2966 <question>
2967 <para>Is this code production-ready?</para>
2968 </question>
2969 <answer>
2970 <para>
2971 By all accounts it is. It is known to work without a
2972 problem on many-thousands-of-hits-per-day webservers. Does
2973 that mean it is 100% bug free? Well, no software is, but
2974 it is well-tested and believed to be fully compatible with
2975 production environments. (The usual disclaimers apply.
2976 This software is provided without warranty of any kind.)
2977 </para>
2978 </answer>
2979 </qandaentry>
2980 <qandaentry>
2981 <question>
2982 <para>Who's using mod_log_sql?</para>
2983 </question>
2984 <answer>
2985 <para>
2986 Good question! It would be great to find out! If you are a
2987 production-level mod_log_sql user, please contact eddie at
2988 &EmailContact;
2989 so that you can be mentioned here.
2990 </para>
2991 </answer>
2992 </qandaentry>
2993 <qandaentry>
2994 <question>
2995 <para>
2996 Why doesn't the module also replace the Apache ErrorLog?
2997 </para>
2998 </question>
2999 <answer>
3000 <para>
3001 There are circumstances when that would be quite unwise --
3002 for example, if Apache could not reach the MySQL server
3003 for some reason and needed to log that fact. Without a
3004 text-based error log you'd never know anything was wrong,
3005 because Apache would be trying to log a database
3006 connection error to the database... you get the point.
3007 </para>
3008 </answer>
3009 <answer>
3010 <para>
3011 Error logs are usually not very high-traffic and are
3012 really best left as text files on a web server machine.
3013 </para>
3014 </answer>
3015 <answer>
3016 <para>
3017 The Error log is free format text.. (no specified
3018 formatting what, so ever) which is rather difficult to
3019 nicely format for storing in a database.
3020 </para>
3021 </answer>
3022 </qandaentry>
3023 <qandaentry>
3024 <question>
3025 <para>Does mod_log_sql work with Apache 2.x?</para>
3026 </question>
3027 <answer>
3028 <para>
3029 Yes. A port of mod_log_sql is available for Apache 2.x as
3030 of mod_log_sql 1.90
3031 </para>
3032 </answer>
3033 </qandaentry>
3034 <qandaentry>
3035 <question>
3036 <para>
3037 Does mod_log_sql connect to MySQL via TCP/IP or a socket?
3038 </para>
3039 </question>
3040 <answer>
3041 <para>Quick answer, Yes.</para>
3042 </answer>
3043 <answer>
3044 <para>
3045 It depends! This is not determined by mod_log_sql.
3046 mod_log_sql relies on a connection command that is
3047 supplied in the MySQL API, and that command is somewhat
3048 intelligent. How it works:
3049 </para>
3050 <itemizedlist>
3051 <listitem>
3052 <simpara>
3053 if the specified MySQL database is on the same
3054 machine, the connection command uses a socket to
3055 communicate with MySQL
3056 </simpara>
3057 </listitem>
3058 <listitem>
3059 <simpara>
3060 if the specified MySQL database is on a different
3061 machine, mod_log_sql connects using TCP/IP.
3062 </simpara>
3063 </listitem>
3064 </itemizedlist>
3065 <para>
3066 You don't have any control of which methodology is used.
3067 You can fine-tune some of the configuration, however. The
3068 LogSQLSocketFile runtime configuration directive overrides
3069 the default of "/var/lib/mysql/mysql.sock" for
3070 socket-based connections, whereas the LogSQLTCPPort
3071 command allows to you override the default TCP port of
3072 3306 for TCP/IP connections.
3073 </para>
3074 </answer>
3075 </qandaentry>
3076 <qandaentry>
3077 <question>
3078 <para>I have discovered a bug. Who can I contact?</para>
3079 </question>
3080 <answer>
3081 <para>
3082 Please contact Edward Rudd at
3083 &EmailContact;
3084 , or post a message to the mod_log_sql
3085 <xref endterm="Sect.MailingLists.title"
3086 linkend="Sect.MailingLists" />
3087 . Your comments, suggestions, bugfixes, bug catches, and
3088 usage testimonials are always welcome. As free software,
3089 mod_log_sql is intended to be a community effort -- any
3090 code contributions or other ideas will be fully and openly
3091 credited, of course.
3092 </para>
3093 </answer>
3094 </qandaentry>
3095 </qandadiv>
3096 <qandadiv>
3097 <title>Problems</title>
3098 <qandaentry>
3099 <question>
3100 <para>
3101 Apache segfaults or has other problems when using PHP and
3102 mod_log_sql
3103 </para>
3104 </question>
3105 <answer>
3106 <para>
3107 This occurs if you compiled PHP with MySQL database
3108 support. PHP utilizes its internal, bundled MySQL
3109 libraries by default. These conflict with the "real" MySQL
3110 libraries linked by mod_log_sql, causing the segmentation
3111 fault.
3112 </para>
3113 <para>
3114 PHP and mod_log_sql can be configured to happily coexist.
3115 The solution is to configure PHP to link against the real
3116 MySQL libraries: recompile PHP using
3117 --with-mysql=/your/path. Apache will run properly once the
3118 modules are all using the same version of the MySQL
3119 libraries.
3120 </para>
3121 </answer>
3122 </qandaentry>
3123 <qandaentry id="FAQ.NothingLogged">
3124 <question>
3125 <para>
3126 Apache appears to start up fine, but nothing is getting
3127 logged in the database
3128 </para>
3129 </question>
3130 <answer>
3131 <para>
3132 If you do not see any entries in the access_log, then
3133 something is preventing the inserts from happening. This
3134 could be caused by several things:
3135 </para>
3136 <itemizedlist>
3137 <listitem>
3138 <simpara>
3139 Improper privileges set up in the MySQL database
3140 </simpara>
3141 </listitem>
3142 <listitem>
3143 <simpara>
3144 You are not hitting a VirtualHost that has a
3145 LogSQLTransferLogTable entry
3146 </simpara>
3147 </listitem>
3148 <listitem>
3149 <simpara>
3150 You did not specify the right database host or login
3151 information
3152 </simpara>
3153 </listitem>
3154 <listitem>
3155 <simpara>
3156 Another factor is preventing a connection to the
3157 database
3158 </simpara>
3159 </listitem>
3160 </itemizedlist>
3161 <note>
3162 <para>
3163 It is improper to ask for help before you have followed
3164 these steps.
3165 </para>
3166 </note>
3167 <para>
3168 First examine the MySQL log that you established in step
3169 <xref linkend="Item.EnableLogging" />
3170 of section
3171 <xref endterm="Sect.Preperation.title"
3172 linkend="Sect.Preperation" />
3173 . Ensure that the INSERT statements are not being rejected
3174 because of a malformed table name or other typographical
3175 error. By enabling that log, you instructed MySQL to log
3176 every connection and command it receives -- if you see no
3177 INSERT attempts in the log, the module isn't successfully
3178 connecting to the database. If you see nothing at all in
3179 the log -- not even a record of your administrative
3180 connection attempts, then you did not enable the log
3181 correctly. If you do see INSERT attempts but they are
3182 failing, the log should tell you why.
3183 </para>
3184 <para>
3185 Second, confirm that your LogSQL* directives are all
3186 correct.
3187 </para>
3188 <para>
3189 Third, examine the Apache error logs for messages from
3190 mod_log_sql; the module will offer hints as to why it
3191 cannot connect, etc.
3192 </para>
3193 <para>
3194 The next thing to do is to change the LogLevel directive
3195 <emphasis>
3196 in the main server config as well as in each VirtualHost
3197 config:
3198 </emphasis>
3199 </para>
3200 <programlisting>LogLevel debug
3201ErrorLog /var/log/httpd/server-messages</programlisting>
3202 </answer>
3203 </qandaentry>
3204 <qandaentry>
3205 <question>
3206 <para>
3207 Why do I get the message "insufficient configuration info
3208 to establish database link" in my Apache error log?
3209 </para>
3210 </question>
3211 <answer>
3212 <para>
3213 At a minimum, LogSQLLoginInfo in the URl form and either
3214 LogSQLTableName or LogSQLMassVirtualHosting must be
3215 defined in order for the module to be able to establish a
3216 database link. If these are not defined or are incomplete
3217 you will receive this error message.
3218 </para>
3219 </answer>
3220 </qandaentry>
3221 <qandaentry>
3222 <question>
3223 <para>
3224 My database cannot handle all the open connections from
3225 mod_log_sql, is there anything I can do?
3226 </para>
3227 </question>
3228 <answer>
3229 <para>
3230 The rule of thumb: if you have n webservers each
3231 configured to support y MaxClients, then your database
3232 must be able to handle n times y simultaneous connections
3233 in the worst case. Certainly you must use common sense,
3234 consider reasonable traffic expectations and structure
3235 things accordingly.
3236 </para>
3237 </answer>
3238 <answer>
3239 <para>
3240 Tweaking my.cnf to scale to high connection loads is
3241 imperative. But if hardware limitations prevent your MySQL
3242 server from gracefully handling the number of incoming
3243 connections, it would be beneficial to upgrade the memory
3244 or CPU on that server in order to handle the load.
3245 </para>
3246 </answer>
3247 <answer>
3248 <para>
3249 Jeremy Zawodny, a highly respected MySQL user and
3250 contributor to Linux Magazine, has this very helpful and
3251 highly appropriate article on tuning MySQL:
3252 <ulink
3253 url="http://jeremy.zawodny.com/blog/archives/000173.html">
3254 MySQL, Linux, and Thread Caching
3255 </ulink>
3256 </para>
3257 </answer>
3258 <answer>
3259 <para>
3260 Please remember that mod_log_sql's overriding principle is
3261 performance -- that is what the target audience demands
3262 and expects. Other database logging solutions do not open
3263 and maintain many database connections, but their
3264 performance suffers drastically. For example, pgLOGd
3265 funnels all log connections through a separate daemon that
3266 connects to the database, but that bottlenecks the entire
3267 process. mod_log_sql achieves performance numbers an order
3268 of magnitude greater than the alternatives because it
3269 dispenses with the overhead associated with rapid
3270 connection cycling, and it does not attempt to shoehorn
3271 all the database traffic through a single extra daemon or
3272 proxy process.
3273 </para>
3274 </answer>
3275 <answer>
3276 <note>
3277 <para>
3278 Currently connection pooling is being implemented as
3279 part of the Database Abstraction layer to allow multiple
3280 httpd processes to share connections.
3281 </para>
3282 </note>
3283 </answer>
3284 </qandaentry>
3285 <qandaentry>
3286 <question>
3287 <para>
3288 Why do I occasionally see a "lost connection to MySQL
3289 server" message in my Apache error log?
3290 </para>
3291 </question>
3292 <answer>
3293 <para>
3294 This message may appear every now and then in your Apache
3295 error log, especially on very lightly loaded servers. This
3296 does not mean that anything is necessarily wrong. Within
3297 each httpd child process, mod_log_sql will open (and keep
3298 open) a connection to the MySQL server. MySQL, however,
3299 will close connections that have not been used in a while;
3300 the default timeout is 8 hours. When this occurs,
3301 mod_log_sql will notice and re-open the connection. That
3302 event is what is being logged, and looks like this:
3303 </para>
3304 <programlisting>[Tue Nov 12 19:04:10 2002] [error] mod_log_sql: first attempt failed,
3305 API said: error 2013, Lost connection to MySQL server during query
3306[Tue Nov 12 19:04:10 2002] [error] mod_log_sql: reconnect successful
3307[Tue Nov 12 19:04:10 2002] [error] mod_log_sql: second attempt successful</programlisting>
3308 <para>
3309 Reference:
3310 <ulink
3311 url="http://dev.mysql.com/doc/mysql/en/Gone_away.html">
3312 MySQL documentation
3313 </ulink>
3314 </para>
3315 </answer>
3316 </qandaentry>
3317 <qandaentry>
3318 <question>
3319 <para>
3320 Sometimes a single VirtualHost gets logged to two
3321 different tables (e.g. access_foo_com,
3322 access_www_foo_com). Or, accesses to an unqualified
3323 hostname (e.g. "http://intranet/index.html") get logged in
3324 separate tables.
3325 </para>
3326 </question>
3327 <answer>
3328 <para>
3329 Proper usage of the Apache runtime ServerName directive
3330 and the directive UseCanonicalName On (or DNS) are
3331 necessary to prevent this problem. "On" is the default for
3332 UseCanonicalName, and specifies that self-referential URLs
3333 are generated from the ServerName part of your
3334 VirtualHost:
3335 </para>
3336 <para>
3337 With UseCanonicalName on (and in all versions prior to
3338 1.3) Apache will use the ServerName and Port directives to
3339 construct the canonical name for the server. With
3340 UseCanonicalName off Apache will form self-referential
3341 URLs using the hostname and port supplied by the client if
3342 any are supplied (otherwise it will use the canonical
3343 name, as defined above). [From
3344 <ulink
3345 url="http://httpd.apache.org/docs/mod/core.html#usecanonicalname">
3346 the Apache documentation
3347 </ulink>
3348 ]
3349 </para>
3350 <para>
3351 The module inherits Apache's "knowledge" about the server
3352 name being accessed. As long as those two directives are
3353 properly configured, mod_log_sql will log to only one
3354 table per virtual host while using
3355 LogSQLMassVirtualHosting.
3356 </para>
3357 </answer>
3358 </qandaentry>
3359 </qandadiv>
3360 <qandadiv>
3361 <title>Performance and Tuning</title>
3362 <qandaentry>
3363 <question>
3364 <para>How well does it perform?</para>
3365 </question>
3366 <answer>
3367 <para>
3368 mod_log_sql scales to very high loads. Apache 1.3.22 +
3369 mod_log_sql was benchmarked using the "ab" (Apache Bench)
3370 program that comes with the Apache distribution; here are
3371 the results.
3372 </para>
3373 <itemizedlist>
3374 <title>Overall configuration</title>
3375 <listitem>
3376 <simpara>Machine A: Apache webserver</simpara>
3377 </listitem>
3378 <listitem>
3379 <simpara>Machine B: MySQL server</simpara>
3380 </listitem>
3381 <listitem>
3382 <simpara>
3383 Machines A and B connected with 100Mbps Ethernet
3384 </simpara>
3385 </listitem>
3386 <listitem>
3387 <simpara>
3388 Webserver: Celeron 400, 128MB RAM, IDE storage
3389 </simpara>
3390 </listitem>
3391 </itemizedlist>
3392 <example>
3393 <title>Apache configuration</title>
3394 <programlisting>Timeout 300
3395KeepAlive On
3396MaxKeepAliveRequests 100
3397KeepAliveTimeout 15
3398MinSpareServers 5
3399StartServers 10
3400MaxSpareServers 15
3401MaxClients 256
3402MaxRequestsPerChild 5000
3403LogSQLTransferLogFormat AbHhmRSsTUuvc
3404LogSQLWhichCookie Clicks
3405CookieTracking on
3406CookieName Clicks</programlisting>
3407 </example>
3408 <example>
3409 <title>"ab" commandline</title>
3410 <programlisting>./ab -c 10 -t 20 -v 2 -C Clicks=ab_run http://www.hostname.com/target</programlisting>
3411 </example>
3412 <para>
3413 ( 10 concurrent requests; 20 second test; setting a cookie
3414 "Clicks=ab_run"; target = the mod_log_sql homepage. )
3415 </para>
3416 <para>
3417 Ten total ab runs were conducted: five with MySQL logging
3418 enabled, and five with all MySQL directives commented out
3419 of httpd.conf. Then each five were averaged. The results:
3420 </para>
3421 <itemizedlist>
3422 <listitem>
3423 <simpara>
3424 Average of five runs employing MySQL and standard text
3425 logging:
3426 <emphasis>
3427 139.01 requests per second, zero errors.
3428 </emphasis>
3429 </simpara>
3430 </listitem>
3431 <listitem>
3432 <simpara>
3433 Average of five runs employing only standard text
3434 logging:
3435 <emphasis>
3436 139.96 requests per second, zero errors.
3437 </emphasis>
3438 </simpara>
3439 </listitem>
3440 </itemizedlist>
3441 <para>
3442 In other words, any rate-limiting effects on this
3443 particular hardware setup are not caused by MySQL. Note
3444 that although this very simple webserver setup is hardly
3445 cutting-edge -- it is, after all, a fairly small machine
3446 -- 139 requests per second equal over twelve million hits
3447 per day.
3448 </para>
3449 <orderedlist>
3450 <title>
3451 If you run this benchmark yourself, take note of three
3452 things:
3453 </title>
3454 <listitem>
3455 <simpara>
3456 Use a target URL that is on your own webserver :-).
3457 </simpara>
3458 </listitem>
3459 <listitem>
3460 <simpara>
3461 Wait until all your connections are closed out between
3462 runs; after several thousand requests your TCP/IP
3463 stack will be filled with hundreds of connections in
3464 TIME_WAIT that need to close. Do a "netstat -t|wc -l"
3465 on the webserver to see. If you don't wait, you can
3466 expect to see a lot of messages like "ip_conntrack:
3467 table full, dropping packet" in your logs. (This has
3468 nothing to do with mod_log_sql, this is simply the
3469 nature of the TCP/IP stack in the Linux kernel.)
3470 </simpara>
3471 </listitem>
3472 <listitem>
3473 <simpara>
3474 When done with your runs, clean these many thousands
3475 of requests out of your database:
3476 </simpara>
3477 <programlisting>mysql&gt; delete from access_log where agent like 'ApacheBench%';
3478mysql&gt; optimize table access_log;</programlisting>
3479 </listitem>
3480 </orderedlist>
3481 </answer>
3482 </qandaentry>
3483 <qandaentry>
3484 <question>
3485 <para>
3486 Do I need to be worried about all the running MySQL
3487 children? Will holding open n Apache-to-MySQL connections
3488 consume a lot of memory?
3489 </para>
3490 </question>
3491 <answer>
3492 <para>Short answer: you shouldn't be worried.</para>
3493 </answer>
3494 <answer>
3495 <para>
3496 Long answer: you might be evaluating at the output of "ps
3497 -aufxw" and becoming alarmed at all the 7MB httpd
3498 processes or 22MB mysqld children that you see. Don't be
3499 alarmed. It's true that mod_log_sql opens and holds open
3500 many MySQL connections: each httpd child maintains one
3501 open database connection (and holds it open for
3502 performance reasons). Four webservers, each running 20
3503 Apache children, will hold open 80 MySQL connections,
3504 which means that your MySQL server needs to handle 80
3505 simultaneous connections. In truth, your MySQL server
3506 needs to handle far more than that if traffic to your
3507 website spikes and the Apache webservers spawn off an
3508 additional 30 children each...
3509 </para>
3510 <para>
3511 Fortunately the cost reported by 'ps -aufxw' is deceptive.
3512 This is due to an OS memory-management feature called
3513 "copy-on-write." When you have a number of identical child
3514 processes (e.g. Apache, MySQL), it would appear in "ps" as
3515 though each one occupies a great deal of RAM -- as much as
3516 7MB per httpd child! In actuality each additional child
3517 only occupies a small bit of extra memory -- most of the
3518 memory pages are common to each child and therefore shared
3519 in a "read-only" fashion. The OS can get away with this
3520 because the majority of memory pages for one child are
3521 identical across all children. Instead of thinking of each
3522 child as a rubber stamp of the others, think of each child
3523 as a basket of links to a common memory area.
3524 </para>
3525 <para>
3526 A memory page is only duplicated when it needs to be
3527 written to, hence "copy-on-write." The result is
3528 efficiency and decreased memory consumption. "ps" may
3529 report 7MB per child, but it might really only "cost" 900K
3530 of extra memory to add one more child. It is not correct
3531 to assume that 20 Apache children with a VSZ of 7MB each
3532 equals (2 x 7MB) of memory consumption -- the real answer
3533 is much, much lower. The same "copy-on-write" rules apply
3534 to all your MySQL children: 40 mysqld children @ 22MB each
3535 do not occupy 880MB of RAM.
3536 </para>
3537 <para>
3538 The bottom line: although there is a cost to spawn extra
3539 httpd or mysqld children, that cost is not as great as
3540 "ps" would lead you to believe.
3541 </para>
3542 </answer>
3543 </qandaentry>
3544 <qandaentry>
3545 <question>
3546 <para>
3547 My webserver cannot handle all the traffic that my site
3548 receives, is there anything I can do?
3549 </para>
3550 </question>
3551 <answer>
3552 <para>
3553 If you have exhausted all the tuning possibilities on your
3554 existing server, it is probably time you evaluated the
3555 benefits of clustering two or more webservers together in
3556 a load-balanced fashion. In fact, users of such a setup
3557 are mod_log_sql's target audience!
3558 </para>
3559 </answer>
3560 </qandaentry>
3561 <qandaentry id="FAQ.DelayedInsert">
3562 <question>
3563 <para>
3564 What is the issue with activating delayed inserts?
3565 </para>
3566 </question>
3567 <answer>
3568 <para>
3569 INSERT DELAYED is a specific syntax to MySQL and is not
3570 supported by any other database. Ergo, why is it needed,
3571 and what MySQL deficiency is it working around? INSERT
3572 DELAYED is a kluge.
3573 </para>
3574 </answer>
3575 <answer>
3576 <para>
3577 The MySQL documentation is unclear whether INSERT DELAYED
3578 is even necessary for an optimized database. It says, "The
3579 DELAYED option for the INSERT statement is a
3580 MySQL-specific option that is very useful if you have
3581 clients that can't wait for the INSERT to complete." But
3582 then it goes on to say, "Note that as MyISAM tables
3583 supports concurrent SELECT and INSERT, if there is no free
3584 blocks in the middle of the data file, you very seldom
3585 need to use INSERT DELAYED with MyISAM."
3586 </para>
3587 </answer>
3588 <answer>
3589 <para>
3590 Because INSERT DELAYED returns without waiting for the
3591 data to be written, a hard kill of your MySQL database at
3592 the right (wrong?) moment could lose those logfile
3593 entries.
3594 </para>
3595 </answer>
3596 <answer>
3597 <para>
3598 As of MySQL version 3.23.52, the error return functions
3599 disagree after a failed INSERT DELAYED: mysql_errno()
3600 always returns 0, even if mysql_error() returns a textual
3601 error. I have reported this bug to the MySQL folks.
3602 However, we have no way of knowing what solution they will
3603 adopt to fix this, and with the worst case solution
3604 mod_log_sql would not be able to tell if anything went
3605 wrong with a delayed insert.
3606 </para>
3607 </answer>
3608 <answer>
3609 <para>
3610 Instead of delayed inserts, you may wish to utilize InnoDB
3611 tables (instead of the standard MyISAM tables). InnoDB
3612 tables suppot row-level locking and are recommended for
3613 high-volume databases.
3614 </para>
3615 </answer>
3616 <answer>
3617 <para>
3618 If after understanding these problems you still wish to
3619 enable delayed inserts, section
3620 <xref endterm="Sect.DelayedInsert.title"
3621 linkend="Sect.DelayedInsert" />
3622 discusses how.
3623 </para>
3624 </answer>
3625 </qandaentry>
3626 </qandadiv>
3627 <qandadiv>
3628 <title>"How do I...?" -- accomplishing certain tasks</title>
3629 <qandaentry>
3630 <question>
3631 <para>
3632 How do I extract the data in a format that my analysis
3633 tool can understand?
3634 </para>
3635 </question>
3636 <answer>
3637 <para>
3638 mod_log_sql would be virtually useless if there weren't a
3639 way for you to extract the data from your database in a
3640 somewhat meaningful fashion. To that end there's a Perl
3641 script enclosed with the distribution. That script
3642 (make_combined_log.pl) is designed to extract N-many days
3643 worth of access logs and provide them in a Combined Log
3644 Format output. You can use this very tool right in
3645 /etc/crontab to extract logs on a regular basis so that
3646 your favorite web analysis tool can read them. Or you can
3647 examine the Perl code to construct your own custom tool.
3648 </para>
3649 <para>
3650 For example, let's say that you want your web statistics
3651 updated once per day in the wee hours of the morning. A
3652 good way to accomplish that could be the following entries
3653 in /etc/crontab:
3654 </para>
3655 <programlisting># Generate the temporary apache logs from the MySQL database (for webalizer)
365605 04 * * * root make_combined_log.pl 1 www.grubbybaby.com &gt; /var/log/temp01
3657# Run webalizer on httpd log
365830 04 * * * root webalizer -c /etc/webalizer.conf; rm -f /var/log/temp01</programlisting>
3659 <para>
3660 Or if you have a newer system that puts files in
3661 /etc/cron.daily etc., create a file called "webalizer" in
3662 the cron.daily subdirectory. Use the following as the
3663 contents of your file, and make sure to chmod 755 it when
3664 done.
3665 </para>
3666 <programlisting>#!/bin/sh
3667/usr/local/sbin/make_combined_log.pl 1 www.yourdomain.com &gt; /var/log/httpd/templog
3668/usr/local/bin/webalizer -q -c /etc/webalizer.conf
3669rm -f /var/log/httpd/templog</programlisting>
3670 <para>See? Easy.</para>
3671 </answer>
3672 </qandaentry>
3673 <qandaentry id="FAQ.Cookie">
3674 <question>
3675 <para>How can I log mod_usertrack cookies?</para>
3676 </question>
3677 <answer>
3678 <para>
3679 A number of people like to log mod_usertrack cookies in
3680 their Apache TransferLog to aid in understanding their
3681 visitors' clickstreams. This is accomplished, for example,
3682 with a statement as follows:
3683 </para>
3684 <programlisting>LogFormat "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"" \"%{cookie}n\""</programlisting>
3685 <para>
3686 Naturally it would be nice for mod_log_sql to permit the
3687 admin to log the cookie data as well, so as of version
3688 1.10 you can do this. You need to have already compiled
3689 mod_usertrack into httpd -- it's one of the standard
3690 Apache modules.
3691 </para>
3692 <para>
3693 First make sure you have a column called "cookie" in the
3694 MySQL database to hold the cookies, which can be done as
3695 follows if you already have a working database:
3696 </para>
3697 <programlisting>mysql&gt; alter table acc_log_tbl add column cookie varchar(255);</programlisting>
3698 <para>
3699 Next configure your server to set usertracking cookies as
3700 follows, and make sure you include the new 'c' directive
3701 in your LogSQLTransferLogFormat, which activates cookie
3702 logging. Here's an example:
3703 </para>
3704 <programlisting>&lt;VirtualHost 1.2.3.4&gt;
3705 CookieTracking on
3706 CookieStyle Cookie
3707 CookieName Foobar
3708 LogSQLTransferLogFormat huSUsbTvRAc
3709 LogSQLWhichCookie Foobar
3710&lt;/VirtualHost&gt;</programlisting>
3711 <para>
3712 The first three lines configure mod_usertrack to create a
3713 COOKIE (RFC 2109) format cookie called Foobar. The last
3714 two lines tell mod_log_sql to log cookies named Foobar.
3715 You have to choose which cookie to log because more than
3716 one cookie can/will be sent to the server by the client.
3717 </para>
3718 <para>
3719 Recap: the 'c' character activates cookie logging, and the
3720 LogSQLWhichCookie directive chooses which cookie to log.
3721 </para>
3722 <para>
3723 FYI, you are advised NOT to use CookieStyle Cookie2 -- it
3724 seems that even newer browsers (IE 5.5, etc.) have trouble
3725 with the new COOKIE2 (RFC 2965) format. Just stick with
3726 the standard COOKIE format and you'll be fine.
3727 </para>
3728 <para>
3729 Perform some hits on your server and run a select
3730 </para>
3731 <programlisting>SELECT request_uri,cookie
3732FROM access_log
3733WHERE cookie IS NOT NULL;</programlisting>
3734 <table>
3735 <title></title>
3736 <tgroup cols="2">
3737 <colspec colname="1" />
3738 <colspec colname="2" />
3739 <thead>
3740 <row>
3741 <entry colname="1">request_uri</entry>
3742 <entry colname="2">cookie</entry>
3743 </row>
3744 </thead>
3745 <tbody>
3746 <row>
3747 <entry colname="1">/mod_log_sql/</entry>
3748 <entry colname="2">
3749 ool-18e4.dyn.optonline.net.130051007102700823
3750 </entry>
3751 </row>
3752 <row>
3753 <entry colname="1">/mod_log_sql/usa.gif</entry>
3754 <entry colname="2">
3755 ool-18e4.dyn.optonline.net.130051007102700823
3756 </entry>
3757 </row>
3758 <row>
3759 <entry colname="1">/mod_log_sql/style_1.css</entry>
3760 <entry colname="2">
3761 ool-18e4.dyn.optonline.net.130051007102700823
3762 </entry>
3763 </row>
3764 </tbody>
3765 </tgroup>
3766 </table>
3767 </answer>
3768 </qandaentry>
3769 <qandaentry>
3770 <question>
3771 <para>
3772 What if I want to log more than one cookie? What is the
3773 difference between LogSQLWhichCookie and
3774 LogSQLWhichCookies?
3775 </para>
3776 </question>
3777 <answer>
3778 <para>
3779 As of version 1.17, you have a choice in how you want
3780 cookie logging handled.
3781 </para>
3782 <para>
3783 If you are interested in logging only one cookie per
3784 request, follow the instructions in FAQ entry
3785 <xref linkend="FAQ.Cookie" />
3786 above. That cookie will be logged to a column in the
3787 regular access_log table, and the actual cookie you want
3788 to log is specified with LogSQLWhichCookie. Don't forget
3789 to specify the 'c' character in LogSQLTransferLogFormat.
3790 </para>
3791 <para>
3792 If, however, you need to log multiple cookies per request,
3793 you must employ the LogSQLWhichCookies (note the plural)
3794 directive. The cookies you specify will be logged to a
3795 separate table (as discussed in section
3796 <xref endterm="Sect.MultiTable.title"
3797 linkend="Sect.MultiTable" />
3798 ), and entries in that table will be linked to the regular
3799 access_log entries via the unique ID that is supplied by
3800 mod_unique_id. Without mod_unique_id the information will
3801 still be logged but you will be unable to correlate which
3802 cookies go with which access-requests. Furthermore, with
3803 LogSQLWhichCookies, you do not need to include the 'c'
3804 character in LogSQLTransferLogFormat.
3805 </para>
3806 <para>
3807 LogSQLWhichCookie and LogSQLWhichCookies can coexist
3808 without conflict because they operate on entireley
3809 different tables, but you're better off choosing the one
3810 you need.
3811 </para>
3812 </answer>
3813 </qandaentry>
3814 <qandaentry>
3815 <question>
3816 <para>
3817 What are the SSL logging features, and how do I activate
3818 them?
3819 </para>
3820 </question>
3821 <answer>
3822 <note>
3823 <para>
3824 You do not need to compile SSL support into mod_log_sql
3825 in order to simply use it with a secure site. You only
3826 need to compile SSL support into mod_log_sql if you want
3827 to log SSL-specific data such as the cipher type used,
3828 or the keysize that was negotiated. If that information
3829 is unimportant to you, you can ignore this FAQ.
3830 </para>
3831 </note>
3832 <para>
3833 By adding certain characters to your
3834 LogSQLTransferLogFormat string you can tell mod_log_sql to
3835 log the SSL cipher, the SSL keysize of the connection, and
3836 the maximum keysize that was available. This would let you
3837 tell, for example, which clients were using only
3838 export-grade security to access your secure software area.
3839 </para>
3840 <para>
3841 You can compile mod_log_sql with SSL logging support if
3842 you have the right packages installed. If you already have
3843 an SSL-enabled Apache then you by definition have the
3844 correct packages already installed: OpenSSL and mod_ssl.
3845 </para>
3846 <para>
3847 You need to ensure that your database is set up to log the
3848 SSL data. Issue the following commands to MySQL if your
3849 access table does not already have them:
3850 </para>
3851 <programlisting>mysql&gt; alter table access_log add column ssl_cipher varchar(25);
3852mysql&gt; alter table access_log add column ssl_keysize smallint unsigned;
3853mysql&gt; alter table access_log add column ssl_maxkeysize smallint unsigned;</programlisting>
3854 <para>
3855 Finally configure httpd.conf to activate the SSL fields.
3856 Note that this is only meaningful in a VirtualHost that is
3857 set up for SSL.
3858 </para>
3859 <programlisting>&lt;VirtualHost 1.2.3.4:443&gt;
3860 LogSQLTransferLogFormat AbHhmRSsTUuvcQqz
3861&lt;/VirtualHost&gt;</programlisting>
3862 <para>
3863 You also need to make sure you have the mod_log_sql_ssl
3864 module loaded as well.
3865 </para>
3866 <para>
3867 The last three characters (Qqz) in the directive are the
3868 SSL ones; see section
3869 <xref linkend="Conf.LogSQLTransferLogFormat" />
3870 in the directives documentation for details of the
3871 LogSQLTransferLogFormat directive.
3872 </para>
3873 <para>
3874 Restart Apache, then perform some hits on your server.
3875 Then run the following select statement:
3876 </para>
3877 <programlisting>SELECT remote_host,request_uri,ssl_cipher,ssl_keysize,ssl_maxkeysize
3878FROM access_log
3879WHERE ssl_cipher IS NOT NULL;</programlisting>
3880 <table>
3881 <title></title>
3882 <tgroup cols="5">
3883 <colspec colname="1" />
3884 <colspec colname="2" />
3885 <colspec colname="3" />
3886 <colspec colname="4" />
3887 <colspec colname="5" />
3888 <thead>
3889 <row>
3890 <entry colname="1">remote_host</entry>
3891 <entry colname="2">request_uri</entry>
3892 <entry colname="3">ssl_cipher</entry>
3893 <entry colname="4">ssl_keysize</entry>
3894 <entry colname="5">ssl_maxkeysize</entry>
3895 </row>
3896 </thead>
3897 <tbody>
3898 <row>
3899 <entry colname="1">216.192.52.4</entry>
3900 <entry colname="2">/dir/somefile.html</entry>
3901 <entry colname="3">RC4-MD5</entry>
3902 <entry colname="4">128</entry>
3903 <entry colname="5">128</entry>
3904 </row>
3905 <row>
3906 <entry colname="1">216.192.52.4</entry>
3907 <entry colname="2">/dir/somefile.gif</entry>
3908 <entry colname="3">RC4-MD5</entry>
3909 <entry colname="4">128</entry>
3910 <entry colname="5">128</entry>
3911 </row>
3912 <row>
3913 <entry colname="1">216.192.52.4</entry>
3914 <entry colname="2">/dir/somefile.jpg</entry>
3915 <entry colname="3">RC4-MD5</entry>
3916 <entry colname="4">128</entry>
3917 <entry colname="5">128</entry>
3918 </row>
3919 </tbody>
3920 </tgroup>
3921 </table>
3922 </answer>
3923 </qandaentry>
3924 </qandadiv>
3925 </qandaset>
3926 </section>
3927</article>
diff --git a/include/apache13.h b/include/apache13.h
new file mode 100644
index 0000000..5659420
--- /dev/null
+++ b/include/apache13.h
@@ -0,0 +1,101 @@
1/* $Id: apache13.h 175 2007-10-20 13:18:20Z urkle@drip.ws $ */
2#ifndef APACHE13_H
3#define APACHE13_H
4
5#include "httpd.h"
6#include "http_config.h"
7#include "http_log.h"
8#include "http_core.h"
9
10/* Defines */
11#define AP_MODULE_DECLARE_DATA MODULE_VAR_EXPORT
12#define APR_OFF_T_FMT "ld"
13#define APR_PID_T_FMT "d"
14#define APR_SUCCESS 0
15#define APR_OFFSETOF XtOffsetOf
16
17/** method of declaring a directive with raw argument parsing */
18# define AP_INIT_RAW_ARGS(directive, func, mconfig, where, help) \
19 { directive, func, mconfig, where, RAW_ARGS, help }
20/** method of declaring a directive which takes 1 argument */
21# define AP_INIT_TAKE1(directive, func, mconfig, where, help) \
22 { directive, func, mconfig, where, TAKE1, help }
23/** method of declaring a directive which takes 2 argument */
24# define AP_INIT_TAKE2(directive, func, mconfig, where, help) \
25 { directive, func, mconfig, where, TAKE2, help }
26/** method of declaring a directive which takes multiple arguments */
27# define AP_INIT_ITERATE(directive, func, mconfig, where, help) \
28 { directive, func, mconfig, where, ITERATE, help }
29/** method of declaring a directive which takes 1 or 3 arguments */
30# define AP_INIT_TAKE13(directive, func, mconfig, where, help) \
31 { directive, func, mconfig, where, TAKE13, help }
32/** method of declaring a directive which takes 3 arguments */
33# define AP_INIT_TAKE3(directive, func, mconfig, where, help) \
34 { directive, func, mconfig, where, TAKE3, help }
35/** method of declaring a directive which takes a flag (on/off) as an argument */
36# define AP_INIT_FLAG(directive, func, mconfig, where, help) \
37 { directive, func, mconfig, where, FLAG, help }
38
39/* Types */
40#define apr_pool_t pool
41#define apr_array_header_t array_header
42#define apr_table_t table
43
44#define apr_status_t int
45#define apr_uri_t uri_components
46
47/* Functions */
48#define ap_get_remote_host(a,b,c,d) ap_get_remote_host(a,b,c)
49#define ap_set_deprecated NULL
50
51#define apr_uri_unparse ap_unparse_uri_components
52#define apr_uri_parse ap_parse_uri_components
53#define ap_add_version_component(p,s) ap_add_version_component(s)
54
55#define apr_pool_create(a,b) *(a) = ap_make_sub_pool(b)
56#define apr_pool_destroy ap_destroy_pool
57#define apr_pool_cleanup_register ap_register_cleanup
58#define apr_palloc ap_palloc
59#define apr_pcalloc ap_pcalloc
60#define apr_pstrdup ap_pstrdup
61#define apr_pstrcat ap_pstrcat
62#define apr_psprintf ap_psprintf
63#define apr_snprintf ap_snprintf
64#define ap_strchr strchr
65#define ap_strchr_c strchr
66#define ap_strstr strstr
67#define ap_strstr_c strstr
68
69#define apr_table_set ap_table_set
70#define apr_table_get ap_table_get
71#define apr_table_make ap_make_table
72
73#define apr_array_push ap_push_array
74#define apr_array_make ap_make_array
75#define apr_array_cat ap_array_cat
76#define apr_is_empty_array(t) (((t) == NULL)||((t)->nelts == 0))
77
78#define apr_file_t FILE
79#define apr_file_printf fprintf
80
81#define apr_tolower ap_tolower
82
83void log_error(char *file, int line, int level, apr_status_t status,
84 const server_rec *s, const char *fmt, ...) __attribute__ ((format (printf, 6,7)));
85
86#ifndef WIN32
87inline
88#endif
89void log_error(char *file, int line, int level,
90 apr_status_t status, const server_rec *s, const char *fmt, ...)
91{
92 static char buff[MAX_STRING_LEN];
93 va_list args;
94 va_start(args, fmt);
95 ap_vsnprintf(buff,MAX_STRING_LEN, fmt,args);
96 ap_log_error(file,line,level | APLOG_NOERRNO ,s,"%s",buff);
97 va_end(args);
98}
99
100
101#endif /* APACHE13_H */
diff --git a/include/apache20.h b/include/apache20.h
new file mode 100644
index 0000000..4c755ab
--- /dev/null
+++ b/include/apache20.h
@@ -0,0 +1,25 @@
1/* $Id: apache20.h 125 2004-04-29 18:05:25Z urkle@drip.ws $ */
2#ifndef APACHE20_H
3#define APACHE20_H
4
5#include "apr_strings.h"
6#include "apr_lib.h"
7#include "apr_hash.h"
8#include "apr_optional.h"
9#define APR_WANT_STRFUNC
10#include "apr_want.h"
11#include "apr_tables.h"
12
13#include "ap_config.h"
14
15#include "httpd.h"
16#include "http_config.h"
17#include "http_core.h"
18#include "http_log.h"
19#include "http_protocol.h"
20
21#include "util_time.h"
22
23#define log_error ap_log_error
24
25#endif /* APACHE20_H */
diff --git a/include/mod_log_sql.h b/include/mod_log_sql.h
new file mode 100644
index 0000000..c1d9bff
--- /dev/null
+++ b/include/mod_log_sql.h
@@ -0,0 +1,166 @@
1/* $Id$ */
2
3#ifndef MOD_LOG_SQL_H
4#define MOD_LOG_SQL_H
5
6/* Create a set of LOGSQL_DECLARE(type), LOGSQL_DECLARE_NONSTD(type) and
7 * LOGSQL_DECLARE_DATA with appropriate export and import tags for the platform
8 */
9#if !defined(WIN32)
10#define LOGSQL_DECLARE(type) type
11#define LOGSQL_DECLARE_NONSTD(type) type
12#define LOGSQL_DECLARE_DATA
13#elif defined(LOGSQL_DECLARE_STATIC)
14#define LOGSQL_DECLARE(type) type __stdcall
15#define LOGSQL_DECLARE_NONSTD(type) type
16#define LOGSQL_DECLARE_DATA
17#elif defined(LOGSQL_DECLARE_EXPORT)
18#define LOGSQL_DECLARE(type) __declspec(dllexport) type __stdcall
19#define LOGSQL_DECLARE_NONSTD(type) __declspec(dllexport) type
20#define LOGSQL_DECLARE_DATA __declspec(dllexport)
21#else
22#define LOGSQL_DECLARE(type) __declspec(dllimport) type __stdcall
23#define LOGSQL_DECLARE_NONSTD(type) __declspec(dllimport) type
24#define LOGSQL_DECLARE_DATA __declspec(dllimport)
25#endif
26
27#define LOG_SQL_PLUGIN_VERSION 20080318
28
29/* Registration function for extract functions */
30
31typedef const char *logsql_item_func(request_rec *r, char *a);
32
33
34typedef enum {
35 LOGSQL_FUNCTION_REQ_FINAL = 0,
36 LOGSQL_FUNCTION_REQ_ORIG
37} logsql_function_req;
38
39LOGSQL_DECLARE(void) log_sql_register_function(apr_pool_t *p,
40 const char *alias, logsql_item_func *func,
41 logsql_function_req want_orig_default);
42
43LOGSQL_DECLARE(void) log_sql_register_alias(server_rec *s, apr_pool_t *p,
44 char key, const char *alias);
45
46typedef enum {
47 LOGSQL_DATATYPE_INT = 0,
48 LOGSQL_DATATYPE_SMALLINT,
49 LOGSQL_DATATYPE_VARCHAR,
50 LOGSQL_DATATYPE_CHAR,
51 LOGSQL_DATATYPE_BIGINT
52} logsql_field_datatype;
53
54LOGSQL_DECLARE(void) log_sql_register_field(apr_pool_t *p,
55 const char *alias,
56 const char *funcalias, const char *param,
57 const char *sql_field_name,
58 logsql_field_datatype datatype, apr_size_t size);
59
60LOGSQL_DECLARE(void) log_sql_register_finish(server_rec *s);
61
62/* DB Connection structure holds connection handle */
63typedef struct {
64 int connected; /* Are we connected to the DB */
65 void *handle; /* DB specific connection pointer */
66 apr_pool_t *p; /* Pool to allocate handle off of */
67 apr_table_t *parms; /* DB connection parameters */
68} logsql_dbconnection;
69
70/* open db handle return values*/
71typedef enum {
72 LOGSQL_OPENDB_FAIL = 0,
73 LOGSQL_OPENDB_SUCCESS,
74 LOGSQL_OPENDB_ALREADY,
75 LOGSQL_OPENDB_PRESERVE
76} logsql_opendb_ret;
77
78typedef enum {
79 LOGSQL_QUERY_SUCCESS = 0,
80 LOGSQL_QUERY_FAIL,
81 LOGSQL_QUERY_NOLINK,
82 LOGSQL_QUERY_NOTABLE,
83 LOGSQL_QUERY_PRESERVED
84} logsql_query_ret;
85
86typedef enum {
87 LOGSQL_TABLE_SUCCESS = 0,
88 LOGSQL_TABLE_FAIL
89} logsql_table_ret;
90
91/* Table type to create/log to */
92typedef enum {
93 LOGSQL_TABLE_ACCESS = 0,
94 LOGSQL_TABLE_NOTES,
95 LOGSQL_TABLE_HEADERSOUT,
96 LOGSQL_TABLE_HEADERSIN,
97 LOGSQL_TABLE_COOKIES
98} logsql_tabletype;
99
100/* All Tables */
101#define LOGSQL_TABLE_ALL LOGSQL_TABLE_ACCESS | LOGSQL_TABLE_NOTES | \
102 LOGSQL_TABLE_HEADERSIN | LOGSQL_TABLE_HEADERSOUT | LOGSQL_TABLE_COOKIES
103
104/* MySQL module calls */
105
106/* Registration function for database drivers */
107
108typedef struct {
109 /* name of the provider */
110 const char *providername;
111 /* NULL terminated list of drivers strings */
112 const char **provided_drivers;
113 /* create a connection to the underlying database layer */
114 logsql_opendb_ret (*connect)(server_rec *s, logsql_dbconnection *db);
115 /* disconnect from the underlying database layer */
116 void (*disconnect)(logsql_dbconnection *db);
117 /* escape the SQL statement according to database rules */
118 const char *(*escape)(request_rec *r,const char *from_str, apr_pool_t *p,
119 logsql_dbconnection *db);
120 /* insert a SQL query statement */
121 logsql_query_ret (*insert)(request_rec *r,logsql_dbconnection *db,
122 const char *query);
123 /* create a SQL table named table_name of table_type */
124 logsql_table_ret (*create_table)(request_rec *r, logsql_dbconnection *db,
125 logsql_tabletype table_type, const char *table_name);
126} logsql_dbdriver;
127
128LOGSQL_DECLARE(void) log_sql_register_driver(apr_pool_t *p,
129 logsql_dbdriver *driver);
130
131/* Module initialization Macros */
132#define LOGSQL_MODULE(driver) log_sql_##driver##_module
133#if defined(WITH_APACHE20)
134# define LOGSQL_MODULE_FORWARD(driver) module AP_MODULE_DECLARE_DATA LOGSQL_MODULE(driver)
135# define LOGSQL_REGISTER(driver) \
136 static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); \
137 static void register_hooks(apr_pool_t *p) { \
138 ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); \
139 } \
140 \
141 LOGSQL_MODULE_FORWARD(driver) = { \
142 STANDARD20_MODULE_STUFF, \
143 NULL, NULL, NULL, NULL, NULL, register_hooks }; \
144 static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
145#elif defined(WITH_APACHE13)
146# define LOGSQL_MODULE_FORWARD(driver) module MODULE_VAR_EXPORT LOGSQL_MODULE(driver)
147# define LOGSQL_REGISTER(driver) \
148 static void module_init(server_rec *s, apr_pool_t *p); \
149 LOGSQL_MODULE_FORWARD(driver) = { \
150 STANDARD_MODULE_STUFF, module_init }; \
151 static void module_init(server_rec *s, apr_pool_t *p)
152#endif
153
154#if defined(WITH_APACHE20)
155# define LOGSQL_SHUTDOWN \
156 static
157#endif
158
159
160#if defined(WITH_APACHE20)
161#define LOGSQL_REGISTER_RETURN log_sql_register_finish(s); return OK;
162#elif defined(WITH_APACHE13)
163#define LOGSQL_REGISTER_RETURN log_sql_register_finish(s);
164#endif
165
166#endif /* MOD_LOG_SQL_H */
diff --git a/include/winconfig.h b/include/winconfig.h
new file mode 100644
index 0000000..702c35f
--- /dev/null
+++ b/include/winconfig.h
@@ -0,0 +1,78 @@
1/* config.h. Generated by configure. */
2/* config.h.in. Generated from configure.ac by autoheader. */
3
4/* Define to 1 if you have the <inttypes.h> header file. */
5/* #undef HAVE_INTTYPES_H */
6
7/* Define to 1 if you have the `m' library (-lm). */
8/* #undef HAVE_LIBM */
9
10/* Define to 1 if you have the `mysqlclient' library (-lmysqlclient). */
11#define HAVE_LIBMYSQLCLIENT 1
12
13/* Define to 1 if you have the `z' library (-lz). */
14/* #undef HAVE_LIBZ */
15
16/* Define to 1 if you have the <limits.h> header file. */
17#define HAVE_LIMITS_H 1
18
19/* Define to 1 if you have the <memory.h> header file. */
20#define HAVE_MEMORY_H 1
21
22/* Define to 1 if you have the <mod_ssl.h> header file. */
23/* #undef HAVE_MOD_SSL_H */
24
25/* Define to 1 if you have the `mysql_real_escape_string' function. */
26#define HAVE_MYSQL_REAL_ESCAPE_STRING 1
27
28/* Define to 1 if you have the <stdint.h> header file. */
29/* #undef HAVE_STDINT_H */
30
31/* Define to 1 if you have the <stdlib.h> header file. */
32/* #undef HAVE_STDLIB_H 1
33
34/* Define to 1 if you have the <strings.h> header file. */
35/* #undef HAVE_STRINGS_H */
36
37/* Define to 1 if you have the <string.h> header file. */
38/* #undef HAVE_STRING_H */
39
40/* Define to 1 if you have the <sys/stat.h> header file. */
41/* #undef HAVE_SYS_STAT_H */
42
43/* Define to 1 if you have the <sys/types.h> header file. */
44/* #undef HAVE_SYS_TYPES_H */
45
46/* Define to 1 if you have the <unistd.h> header file. */
47/* #undef HAVE_UNISTD_H */
48
49/* Define to the address where bug reports for this package should be sent. */
50#define PACKAGE_BUGREPORT ""
51
52/* Define to the full name of this package. */
53#define PACKAGE_NAME "mod_log_sql"
54
55/* Define to the full name and version of this package. */
56#define PACKAGE_STRING "mod_log_sql 1.100"
57
58/* Define to the one symbol short name of this package. */
59#define PACKAGE_TARNAME "mod-log-sql"
60
61/* Define to the version of this package. */
62#define PACKAGE_VERSION "1.100"
63
64/* Define to 1 if you have the ANSI C header files. */
65/* #undef STDC_HEADERS */
66
67/* Define if we want to compile in SSL support. */
68/* #undef WANT_SSL_LOGGING */
69
70/* Define to 1 if we are compiling with Apache 1.3.x */
71/* #undef WITH_APACHE13 */
72
73/* Define to 1 if we are compiling with Apache 2.0.x */
74/* #undef WITH_APACHE20 */
75
76/* Define to 1 if we are compiling with mysql */
77#define WITH_MYSQL 1
78
diff --git a/install-sh b/install-sh
new file mode 100755
index 0000000..e9de238
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,251 @@
1#!/bin/sh
2#
3# install - install a program, script, or datafile
4# This comes from X11R5 (mit/util/scripts/install.sh).
5#
6# Copyright 1991 by the Massachusetts Institute of Technology
7#
8# Permission to use, copy, modify, distribute, and sell this software and its
9# documentation for any purpose is hereby granted without fee, provided that
10# the above copyright notice appear in all copies and that both that
11# copyright notice and this permission notice appear in supporting
12# documentation, and that the name of M.I.T. not be used in advertising or
13# publicity pertaining to distribution of the software without specific,
14# written prior permission. M.I.T. makes no representations about the
15# suitability of this software for any purpose. It is provided "as is"
16# without express or implied warranty.
17#
18# Calling this script install-sh is preferred over install.sh, to prevent
19# `make' implicit rules from creating a file called install from it
20# when there is no Makefile.
21#
22# This script is compatible with the BSD install script, but was written
23# from scratch. It can only install one file at a time, a restriction
24# shared with many OS's install programs.
25
26
27# set DOITPROG to echo to test this script
28
29# Don't use :- since 4.3BSD and earlier shells don't like it.
30doit="${DOITPROG-}"
31
32
33# put in absolute paths if you don't have them in your path; or use env. vars.
34
35mvprog="${MVPROG-mv}"
36cpprog="${CPPROG-cp}"
37chmodprog="${CHMODPROG-chmod}"
38chownprog="${CHOWNPROG-chown}"
39chgrpprog="${CHGRPPROG-chgrp}"
40stripprog="${STRIPPROG-strip}"
41rmprog="${RMPROG-rm}"
42mkdirprog="${MKDIRPROG-mkdir}"
43
44transformbasename=""
45transform_arg=""
46instcmd="$mvprog"
47chmodcmd="$chmodprog 0755"
48chowncmd=""
49chgrpcmd=""
50stripcmd=""
51rmcmd="$rmprog -f"
52mvcmd="$mvprog"
53src=""
54dst=""
55dir_arg=""
56
57while [ x"$1" != x ]; do
58 case $1 in
59 -c) instcmd="$cpprog"
60 shift
61 continue;;
62
63 -d) dir_arg=true
64 shift
65 continue;;
66
67 -m) chmodcmd="$chmodprog $2"
68 shift
69 shift
70 continue;;
71
72 -o) chowncmd="$chownprog $2"
73 shift
74 shift
75 continue;;
76
77 -g) chgrpcmd="$chgrpprog $2"
78 shift
79 shift
80 continue;;
81
82 -s) stripcmd="$stripprog"
83 shift
84 continue;;
85
86 -t=*) transformarg=`echo $1 | sed 's/-t=//'`
87 shift
88 continue;;
89
90 -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
91 shift
92 continue;;
93
94 *) if [ x"$src" = x ]
95 then
96 src=$1
97 else
98 # this colon is to work around a 386BSD /bin/sh bug
99 :
100 dst=$1
101 fi
102 shift
103 continue;;
104 esac
105done
106
107if [ x"$src" = x ]
108then
109 echo "install: no input file specified"
110 exit 1
111else
112 true
113fi
114
115if [ x"$dir_arg" != x ]; then
116 dst=$src
117 src=""
118
119 if [ -d $dst ]; then
120 instcmd=:
121 chmodcmd=""
122 else
123 instcmd=mkdir
124 fi
125else
126
127# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
128# might cause directories to be created, which would be especially bad
129# if $src (and thus $dsttmp) contains '*'.
130
131 if [ -f $src -o -d $src ]
132 then
133 true
134 else
135 echo "install: $src does not exist"
136 exit 1
137 fi
138
139 if [ x"$dst" = x ]
140 then
141 echo "install: no destination specified"
142 exit 1
143 else
144 true
145 fi
146
147# If destination is a directory, append the input filename; if your system
148# does not like double slashes in filenames, you may need to add some logic
149
150 if [ -d $dst ]
151 then
152 dst="$dst"/`basename $src`
153 else
154 true
155 fi
156fi
157
158## this sed command emulates the dirname command
159dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
160
161# Make sure that the destination directory exists.
162# this part is taken from Noah Friedman's mkinstalldirs script
163
164# Skip lots of stat calls in the usual case.
165if [ ! -d "$dstdir" ]; then
166defaultIFS='
167'
168IFS="${IFS-${defaultIFS}}"
169
170oIFS="${IFS}"
171# Some sh's can't handle IFS=/ for some reason.
172IFS='%'
173set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
174IFS="${oIFS}"
175
176pathcomp=''
177
178while [ $# -ne 0 ] ; do
179 pathcomp="${pathcomp}${1}"
180 shift
181
182 if [ ! -d "${pathcomp}" ] ;
183 then
184 $mkdirprog "${pathcomp}"
185 else
186 true
187 fi
188
189 pathcomp="${pathcomp}/"
190done
191fi
192
193if [ x"$dir_arg" != x ]
194then
195 $doit $instcmd $dst &&
196
197 if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
198 if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
199 if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
200 if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
201else
202
203# If we're going to rename the final executable, determine the name now.
204
205 if [ x"$transformarg" = x ]
206 then
207 dstfile=`basename $dst`
208 else
209 dstfile=`basename $dst $transformbasename |
210 sed $transformarg`$transformbasename
211 fi
212
213# don't allow the sed command to completely eliminate the filename
214
215 if [ x"$dstfile" = x ]
216 then
217 dstfile=`basename $dst`
218 else
219 true
220 fi
221
222# Make a temp file name in the proper directory.
223
224 dsttmp=$dstdir/#inst.$$#
225
226# Move or copy the file name to the temp name
227
228 $doit $instcmd $src $dsttmp &&
229
230 trap "rm -f ${dsttmp}" 0 &&
231
232# and set any options; do chmod last to preserve setuid bits
233
234# If any of these fail, we abort the whole thing. If we want to
235# ignore errors from any of these, just make sure not to ignore
236# errors from the above "$doit $instcmd $src $dsttmp" command.
237
238 if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
239 if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
240 if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
241 if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
242
243# Now rename the file to the real destination.
244
245 $doit $rmcmd -f $dstdir/$dstfile &&
246 $doit $mvcmd $dsttmp $dstdir/$dstfile
247
248fi &&
249
250
251exit 0
diff --git a/m4 b/m4
new file mode 160000
Subproject 946e50ebb8d52a5db376350b5602feef10fa45f
diff --git a/mod_log_sql.c b/mod_log_sql.c
deleted file mode 100644
index f02243e..0000000
--- a/mod_log_sql.c
+++ /dev/null
@@ -1,1687 +0,0 @@
1
2/* --------*
3 * DEFINES *
4 * --------*/
5
6/* The enduser may wish to modify this */
7#undef DEBUG
8
9/* The enduser won't modify these */
10#define MYSQL_ERROR(mysql) ((mysql)?(mysql_error(mysql)):"MySQL server has gone away")
11#define ERRLEVEL APLOG_ERR|APLOG_NOERRNO
12#define WARNINGLEVEL APLOG_WARNING|APLOG_NOERRNO
13#define NOTICELEVEL APLOG_NOTICE|APLOG_NOERRNO
14#define DEBUGLEVEL APLOG_DEBUG|APLOG_NOERRNO
15
16/* ---------*
17 * INCLUDES *
18 * ---------*/
19#include <time.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include "httpd.h"
24#include "http_config.h"
25#include "http_log.h"
26#include "http_core.h"
27#include "mysql.h"
28#include "mysqld_error.h"
29
30#if MODULE_MAGIC_NUMBER >= 19980324 /* M_M_N is defined in /usr/local/Apache/include/ap_mmn.h, 19990320 as of this writing. */
31 #include "ap_compat.h"
32#endif
33
34#ifdef WANT_SSL_LOGGING
35 #include "mod_ssl.h"
36#endif
37
38
39/* -------------*
40 * DECLARATIONS *
41 * -------------*/
42
43/* Declare ourselves so the configuration routines can find and know us. */
44module sql_log_module;
45
46/* The contents of these are known 'Apache wide' and are not variable
47 * on a per-virtual-server basis. Every virtual server 'knows' the
48 * same versions of these variables.
49 */
50MYSQL logsql_server, *logsql_server_p = NULL;
51
52int logsql_massvirtual = 0;
53int logsql_createtables = 0;
54int logsql_forcepreserve = 0;
55char *logsql_dbname = NULL;
56char *logsql_dbhost = NULL;
57char *logsql_dbuser = NULL;
58char *logsql_dbpwd = NULL;
59char *logsql_machid = NULL;
60char *logsql_socketfile = "/tmp/mysql.sock";
61unsigned int logsql_tcpport = 3306;
62
63#ifdef WANT_DELAYED_MYSQL_INSERT
64 char *logsql_insertclause = "insert delayed into ";
65#else
66 char *logsql_insertclause = "insert into ";
67#endif
68
69typedef const char *(*item_key_func) (request_rec *, char *);
70
71/* But the contents of this structure will vary by virtual server.
72 * This permits each virtual server to vary its configuration slightly
73 * for per-server customization.
74 *
75 * Each child process has its own segregated copy of this structure.
76 */
77typedef struct {
78 array_header *transfer_ignore_list;
79 array_header *transfer_accept_list;
80 array_header *remhost_ignore_list;
81 array_header *notes_list;
82 array_header *hout_list;
83 array_header *hin_list;
84 array_header *cookie_list;
85 char *notes_table_name;
86 char *hout_table_name;
87 char *hin_table_name;
88 char *cookie_table_name;
89 char *transfer_table_name;
90 char *transfer_log_format;
91 char *preserve_file;
92 char *cookie_name;
93} logsql_state;
94
95
96/* -----------------*
97 * HELPER FUNCTIONS *
98 * -----------------*/
99
100int safe_create_tables(logsql_state *cls, request_rec *r);
101
102static char *format_integer(pool *p, int i)
103{
104 char dummy[40];
105 ap_snprintf(dummy, sizeof(dummy), "%d", i);
106 return pstrdup(p, dummy);
107}
108
109static char *pfmt(pool *p, int i)
110{
111 if (i <= 0) {
112 return "-";
113 } else {
114 return format_integer(p, i);
115 }
116}
117
118/* Begin the individual functions that, given a request r,
119 * extract the needed information from it and return the
120 * value to the calling entity.
121 */
122
123static const char *extract_remote_host(request_rec *r, char *a)
124{
125 return (char *) get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME);
126}
127
128static const char *extract_remote_logname(request_rec *r, char *a)
129{
130 return (char *) get_remote_logname(r);
131}
132
133static const char *extract_remote_user(request_rec *r, char *a)
134{
135 char *rvalue = r->connection->user;
136
137 if (rvalue == NULL) {
138 rvalue = "-";
139 } else if (strlen(rvalue) == 0) {
140 rvalue = "\"\"";
141 }
142 return rvalue;
143}
144
145#ifdef WANT_SSL_LOGGING
146static const char *extract_ssl_keysize(request_rec *r, char *a)
147{
148 char *result = NULL;
149
150 if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) {
151 result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_USEKEYSIZE");
152 #ifdef DEBUG
153 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_KEYSIZE: %s", result);
154 #endif
155 if (result != NULL && result[0] == '\0')
156 result = NULL;
157 return result;
158 } else {
159 return "0";
160 }
161}
162
163static const char *extract_ssl_maxkeysize(request_rec *r, char *a)
164{
165 char *result = NULL;
166
167 if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) {
168 result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_ALGKEYSIZE");
169 #ifdef DEBUG
170 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_ALGKEYSIZE: %s", result);
171 #endif
172 if (result != NULL && result[0] == '\0')
173 result = NULL;
174 return result;
175 } else {
176 return "0";
177 }
178}
179
180static const char *extract_ssl_cipher(request_rec *r, char *a)
181{
182 char *result = NULL;
183
184 if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) {
185 result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER");
186 #ifdef DEBUG
187 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_CIPHER: %s", result);
188 #endif
189 if (result != NULL && result[0] == '\0')
190 result = NULL;
191 return result;
192 } else {
193 return "-";
194 }
195}
196#endif /* WANT_SSL_LOGGING */
197
198static const char *extract_request_method(request_rec *r, char *a)
199{
200 return r->method;
201}
202
203static const char *extract_request_protocol(request_rec *r, char *a)
204{
205 return r->protocol;
206}
207
208static const char *extract_request_line(request_rec *r, char *a)
209{
210 return r->the_request;
211}
212
213static const char *extract_request_file(request_rec *r, char *a)
214{
215 return r->filename;
216}
217
218static const char *extract_request_uri(request_rec *r, char *a)
219{
220 return r->uri;
221}
222
223static const char *extract_request_args(request_rec *r, char *a)
224{
225 return r->args;
226}
227
228static const char *extract_status(request_rec *r, char *a)
229{
230 return pfmt(r->pool, r->status);
231}
232
233static const char *extract_bytes_sent(request_rec *r, char *a)
234{
235 if (!r->sent_bodyct) {
236 return "-";
237 } else {
238 long int bs;
239 char dummy[40];
240 bgetopt(r->connection->client, BO_BYTECT, &bs);
241 ap_snprintf(dummy, sizeof(dummy), "%ld", bs);
242 return pstrdup(r->pool, dummy);
243 }
244}
245
246/*
247static const char *extract_header_in(request_rec *r, char *a)
248{
249 return table_get(r->headers_in, a);
250}
251
252static const char *extract_header_out(request_rec *r, char *a)
253{
254 const char *cp = table_get(r->headers_out, a);
255 if (!strcasecmp(a, "Content-type") && r->content_type) {
256 cp = r->content_type;
257 }
258 if (cp) {
259 return cp;
260 }
261 return table_get(r->err_headers_out, a);
262}
263*/
264
265static const char *extract_request_time(request_rec *r, char *a)
266{
267 int timz;
268 struct tm *t;
269 char tstr[MAX_STRING_LEN];
270
271 t = get_gmtoff(&timz);
272
273 if (a && *a) { /* Custom format */
274 strftime(tstr, MAX_STRING_LEN, a, t);
275 } else { /* CLF format */
276 char sign = (timz < 0 ? '-' : '+');
277
278 if (timz < 0) {
279 timz = -timz;
280 }
281 strftime(tstr, MAX_STRING_LEN, "[%d/%b/%Y:%H:%M:%S ", t);
282 ap_snprintf(tstr + strlen(tstr), sizeof(tstr) - strlen(tstr), "%c%.2d%.2d]", sign, timz / 60, timz % 60);
283 }
284
285 return pstrdup(r->pool, tstr);
286}
287
288static const char *extract_request_duration(request_rec *r, char *a)
289{
290 char duration[22]; /* Long enough for 2^64 */
291
292 ap_snprintf(duration, sizeof(duration), "%ld", time(NULL) - r->request_time);
293 return pstrdup(r->pool, duration);
294}
295
296static const char *extract_virtual_host(request_rec *r, char *a)
297{
298 return ap_get_server_name(r);
299}
300
301static const char *extract_machine_id(request_rec *r, char *a)
302{
303 if (!logsql_machid)
304 return "-";
305 else
306 return logsql_machid;
307}
308
309static const char *extract_server_port(request_rec *r, char *a)
310{
311 char portnum[22];
312
313 ap_snprintf(portnum, sizeof(portnum), "%u", r->server->port);
314 return pstrdup(r->pool, portnum);
315}
316
317static const char *extract_child_pid(request_rec *r, char *a)
318{
319 char pidnum[22];
320 ap_snprintf(pidnum, sizeof(pidnum), "%ld", (long) getpid());
321 return pstrdup(r->pool, pidnum);
322}
323
324static const char *extract_referer(request_rec *r, char *a)
325{
326 const char *tempref;
327
328 tempref = table_get(r->headers_in, "Referer");
329 if (!tempref)
330 {
331 return "-";
332 } else {
333 return tempref;
334 }
335}
336
337static const char *extract_agent(request_rec *r, char *a)
338{
339 const char *tempag;
340
341 tempag = table_get(r->headers_in, "User-Agent");
342 if (!tempag)
343 {
344 return "-";
345 } else {
346 return tempag;
347 }
348}
349
350static const char *extract_cookie(request_rec *r, char *a)
351{
352 const char *cookiestr;
353 char *cookieend;
354 char *isvalid;
355 char *cookiebuf;
356
357 logsql_state *cls = get_module_config(r->server->module_config, &sql_log_module);
358
359 if (cls->cookie_name != NULL) {
360 #ifdef DEBUG
361 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"watching for cookie '%s'", cls->cookie_name);
362 #endif
363
364 /* Fetch out the cookie header */
365 cookiestr = (char *)table_get(r->headers_in, "cookie2");
366 if (cookiestr != NULL) {
367 #ifdef DEBUG
368 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie2: [%s]", cookiestr);
369 #endif
370 /* Does the cookie string contain one with our name? */
371 isvalid = strstr(cookiestr, cls->cookie_name);
372 if (isvalid != NULL) {
373 /* Move past the cookie name and equal sign */
374 isvalid += strlen(cls->cookie_name) + 1;
375 /* Duplicate it into the pool */
376 cookiebuf = ap_pstrdup(r->pool, isvalid);
377 /* Segregate just this cookie out of the string
378 * with a terminating nul at the first semicolon */
379 cookieend = strchr(cookiebuf, ';');
380 if (cookieend != NULL)
381 *cookieend = '\0';
382 return cookiebuf;
383 }
384 }
385
386 cookiestr = (char *)table_get(r->headers_in, "cookie");
387 if (cookiestr != NULL) {
388 #ifdef DEBUG
389 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie: [%s]", cookiestr);
390 #endif
391 isvalid = strstr(cookiestr, cls->cookie_name);
392 if (isvalid != NULL) {
393 isvalid += strlen(cls->cookie_name) + 1;
394 cookiebuf = ap_pstrdup(r->pool, isvalid);
395 cookieend = strchr(cookiebuf, ';');
396 if (cookieend != NULL)
397 *cookieend = '\0';
398 return cookiebuf;
399 }
400 }
401
402 cookiestr = table_get(r->headers_out, "set-cookie");
403 if (cookiestr != NULL) {
404 #ifdef DEBUG
405 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Set-Cookie: [%s]", cookiestr);
406 #endif
407 isvalid = strstr(cookiestr, cls->cookie_name);
408 if (isvalid != NULL) {
409 isvalid += strlen(cls->cookie_name) + 1;
410 cookiebuf = ap_pstrdup(r->pool, isvalid);
411 cookieend = strchr(cookiebuf, ';');
412 if (cookieend != NULL)
413 *cookieend = '\0';
414 return cookiebuf;
415 }
416 }
417 }
418
419 return "-";
420}
421
422static const char *extract_specific_cookie(request_rec *r, char *a)
423{
424 const char *cookiestr;
425 char *cookieend;
426 char *isvalid;
427 char *cookiebuf;
428
429 if (a != NULL) {
430 #ifdef DEBUG
431 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"watching for cookie '%s'", a);
432 #endif
433
434 /* Fetch out the cookie header */
435 cookiestr = (char *)table_get(r->headers_in, "cookie2");
436 if (cookiestr != NULL) {
437 #ifdef DEBUG
438 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie2: [%s]", cookiestr);
439 #endif
440 /* Does the cookie string contain one with our name? */
441 isvalid = strstr(cookiestr, a);
442 if (isvalid != NULL) {
443 /* Move past the cookie name and equal sign */
444 isvalid += strlen(a) + 1;
445 /* Duplicate it into the pool */
446 cookiebuf = ap_pstrdup(r->pool, isvalid);
447 /* Segregate just this cookie out of the string
448 * with a terminating nul at the first semicolon */
449 cookieend = strchr(cookiebuf, ';');
450 if (cookieend != NULL)
451 *cookieend = '\0';
452 return cookiebuf;
453 }
454 }
455
456 cookiestr = (char *)table_get(r->headers_in, "cookie");
457 if (cookiestr != NULL) {
458 #ifdef DEBUG
459 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie: [%s]", cookiestr);
460 #endif
461 isvalid = strstr(cookiestr, a);
462 if (isvalid != NULL) {
463 isvalid += strlen(a) + 1;
464 cookiebuf = ap_pstrdup(r->pool, isvalid);
465 cookieend = strchr(cookiebuf, ';');
466 if (cookieend != NULL)
467 *cookieend = '\0';
468 return cookiebuf;
469 }
470 }
471
472 cookiestr = table_get(r->headers_out, "set-cookie");
473 if (cookiestr != NULL) {
474 #ifdef DEBUG
475 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Set-Cookie: [%s]", cookiestr);
476 #endif
477 isvalid = strstr(cookiestr, a);
478 if (isvalid != NULL) {
479 isvalid += strlen(a) + 1;
480 cookiebuf = ap_pstrdup(r->pool, isvalid);
481 cookieend = strchr(cookiebuf, ';');
482 if (cookieend != NULL)
483 *cookieend = '\0';
484 return cookiebuf;
485 }
486 }
487 }
488
489 return "-";
490}
491
492
493static const char *extract_request_timestamp(request_rec *r, char *a)
494{
495 char tstr[32];
496
497 ap_snprintf(tstr, 32, "%ld", time(NULL));
498 return pstrdup(r->pool, tstr);
499}
500
501/*
502static const char *extract_note(request_rec *r, char *a)
503{
504 return ap_table_get(r->notes, a);
505
506}
507*/
508
509static const char *extract_env_var(request_rec *r, char *a)
510{
511 return ap_table_get(r->subprocess_env, a);
512}
513
514static const char *extract_unique_id(request_rec *r, char *a)
515{
516 const char *tempid;
517
518 tempid = ap_table_get(r->subprocess_env, "UNIQUE_ID");
519 if (!tempid)
520 return "-";
521 else
522 return tempid;
523}
524
525/* End declarations of various extract_ functions */
526
527
528
529struct log_sql_item_list {
530 char ch; /* its letter code */
531 item_key_func func; /* its extraction function */
532 const char *sql_field_name; /* its column in SQL */
533 int want_orig_default; /* if it requires the original request prior to internal redirection */
534 int string_contents; /* if it returns a string */
535 } log_sql_item_keys[] = {
536
537 { 'A', extract_agent, "agent", 1, 1 },
538 { 'a', extract_request_args, "request_args", 1, 1 },
539 { 'b', extract_bytes_sent, "bytes_sent", 0, 0 },
540 { 'c', extract_cookie, "cookie", 0, 1 },
541 { 'e', extract_env_var, "env_var", 0, 1 },
542 { 'f', extract_request_file, "request_file", 0, 1 },
543 { 'H', extract_request_protocol, "request_protocol", 0, 1 },
544 { 'h', extract_remote_host, "remote_host", 0, 1 },
545 { 'I', extract_unique_id, "id", 0, 1 },
546 { 'l', extract_remote_logname, "remote_logname", 0, 1 },
547 { 'm', extract_request_method, "request_method", 0, 1 },
548 { 'M', extract_machine_id, "machine_id", 0, 1 },
549 { 'P', extract_child_pid, "child_pid", 0, 0 },
550 { 'p', extract_server_port, "server_port", 0, 0 },
551 { 'R', extract_referer, "referer", 1, 1 },
552 { 'r', extract_request_line, "request_line", 1, 1 },
553 { 'S', extract_request_timestamp, "time_stamp", 0, 0 },
554 { 's', extract_status, "status", 1, 0 },
555 { 'T', extract_request_duration, "request_duration", 1, 0 },
556 { 't', extract_request_time, "request_time", 0, 1 },
557 { 'u', extract_remote_user, "remote_user", 0, 1 },
558 { 'U', extract_request_uri, "request_uri", 1, 1 },
559 { 'v', extract_virtual_host, "virtual_host", 0, 1 },
560 #ifdef WANT_SSL_LOGGING
561 { 'q', extract_ssl_keysize, "ssl_keysize", 0, 1 },
562 { 'Q', extract_ssl_maxkeysize, "ssl_maxkeysize", 0, 1 },
563 { 'z', extract_ssl_cipher, "ssl_cipher", 0, 1 },
564 #endif
565 {'\0'}
566};
567
568
569/* Routine to escape the 'dangerous' characters that would otherwise
570 * corrupt the INSERT string: ', \, and "
571 */
572const char *escape_query(const char *from_str, pool *p)
573{
574 if (!from_str)
575 return NULL;
576 else {
577 char *to_str;
578 unsigned long length = strlen(from_str);
579 unsigned long retval;
580
581 /* Pre-allocate a new string that could hold twice the original, which would only
582 * happen if the whole original string was 'dangerous' characters.
583 */
584 to_str = (char *) ap_palloc(p, length * 2 + 1);
585 if (!to_str) {
586 return from_str;
587 }
588
589 if (!logsql_server_p) {
590 /* Well, I would have liked to use the current database charset. mysql is
591 * unavailable, however, so I fall back to the slightly less respectful
592 * mysql_escape_string() function that uses the default charset.
593 */
594 retval = mysql_escape_string(to_str, from_str, length);
595 } else {
596 /* MySQL is available, so I'll go ahead and respect the current charset when
597 * I perform the escape.
598 */
599 retval = mysql_real_escape_string(logsql_server_p, to_str, from_str, length);
600 }
601
602 if (retval)
603 return to_str;
604 else
605 return from_str;
606 }
607}
608
609int open_logdb_link(server_rec* s)
610{
611 /* Returns:
612 3 if preserve forced
613 2 if already connected
614 1 if successful
615 0 if unsuccessful
616 */
617
618 if (logsql_forcepreserve)
619 return 3;
620
621 if (logsql_server_p)
622 return 2;
623
624 if ((logsql_dbname) && (logsql_dbhost) && (logsql_dbuser) && (logsql_dbpwd)) {
625 mysql_init(&logsql_server);
626 logsql_server_p = mysql_real_connect(&logsql_server, logsql_dbhost, logsql_dbuser, logsql_dbpwd, logsql_dbname, logsql_tcpport, logsql_socketfile, 0);
627
628 if (logsql_server_p) {
629 #ifdef DEBUG
630 ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
631 logsql_dbhost, logsql_tcpport, logsql_dbname, logsql_dbuser, logsql_socketfile);
632 #endif
633 return 1;
634 } else {
635 #ifdef DEBUG
636 ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"mod_log_sql: database connection error: %s",MYSQL_ERROR(&logsql_server));
637 ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
638 logsql_dbhost, logsql_tcpport, logsql_dbname, logsql_dbuser, logsql_socketfile);
639 #endif
640 return 0;
641 }
642 } else {
643 ap_log_error(APLOG_MARK,ERRLEVEL,s,"mod_log_sql: insufficient configuration info to establish database link");
644 return 0;
645 }
646}
647
648const char *extract_table(void *data, const char *key, const char *val)
649{
650 request_rec *r = (request_rec *)data;
651
652 return ap_pstrcat(r->pool, key, " = ", val, " ", NULL);
653}
654
655void preserve_entry(request_rec *r, const char *query)
656{
657 FILE *fp;
658 logsql_state *cls = get_module_config(r->server->module_config, &sql_log_module);
659
660 fp = pfopen(r->pool, cls->preserve_file, "a");
661 if (fp == NULL)
662 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: attempted append of local preserve file but failed.");
663 else {
664 fprintf(fp,"%s;\n", query);
665 pfclose(r->pool, fp);
666 #ifdef DEBUG
667 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: entry preserved in %s", cls->preserve_file);
668 #endif
669 }
670}
671
672
673/*-----------------------------------------------------*/
674/* safe_sql_query: perform a database query with */
675/* a degree of safety and error checking. */
676/* */
677/* Parms: request record, SQL insert statement */
678/* Returns: 0 (OK) on success */
679/* 1 if have no log handle */
680/* 2 if insert delayed failed (kluge) */
681/* the actual MySQL return code on error */
682/*-----------------------------------------------------*/
683unsigned int safe_sql_query(request_rec *r, const char *query)
684{
685 int retval;
686 struct timespec delay, remainder;
687 int ret;
688 void (*handler) (int);
689 logsql_state *cls;
690 unsigned int real_error;
691 #ifdef WANT_DELAYED_MYSQL_INSERT
692 char *real_error_str;
693 #endif
694
695 /* A failed mysql_query() may send a SIGPIPE, so we ignore that signal momentarily. */
696 handler = signal(SIGPIPE, SIG_IGN);
697
698 /* First attempt for the query */
699 if (!logsql_server_p) {
700 signal(SIGPIPE, handler);
701 return 1;
702 } else if (!(retval = mysql_query(logsql_server_p, query))) {
703 signal(SIGPIPE, handler);
704 return 0;
705 }
706
707 /* If we ran the query and it returned an error, try to be robust.
708 * (After all, the module thought it had a valid mysql_log connection but the query
709 * could have failed for a number of reasons, so we have to be extra-safe and check.) */
710 #ifdef WANT_DELAYED_MYSQL_INSERT
711 real_error_str = MYSQL_ERROR(logsql_server_p);
712 #else
713 real_error = mysql_errno(logsql_server_p);
714 #endif
715
716 /* Check to see if the error is "nonexistent table" */
717 #ifdef WANT_DELAYED_MYSQL_INSERT
718 if ((strstr(real_error_str, "Table")) && (strstr(real_error_str,"doesn't exist"))) {
719 #else
720 if (real_error == ER_NO_SUCH_TABLE) {
721 #endif
722 if (logsql_createtables) {
723 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: table doesn't exist...creating now");
724 cls = get_module_config(r->server->module_config, &sql_log_module);
725 if (safe_create_tables(cls, r)) {
726 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: child attempted but failed to create one or more tables for %s, preserving query", ap_get_server_name(r));
727 preserve_entry(r, query);
728 retval = mysql_errno(logsql_server_p);
729 } else {
730 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: tables successfully created - retrying query");
731 if (mysql_query(logsql_server_p, query)) {
732 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: giving up, preserving query");
733 preserve_entry(r, query);
734 retval = mysql_errno(logsql_server_p);
735 } else
736 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: query successful after table creation");
737 retval = 0;
738 }
739 } else {
740 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql, table doesn't exist, creation denied by configuration, preserving query");
741 preserve_entry(r, query);
742 retval = ER_NO_SUCH_TABLE;
743 }
744 /* Restore SIGPIPE to its original handler function */
745 signal(SIGPIPE, handler);
746 return retval;
747 }
748
749 /* Handle all other types of errors */
750
751 cls = get_module_config(r->server->module_config, &sql_log_module);
752
753 /* Something went wrong, so start by trying to restart the db link. */
754 #ifdef WANT_DELAYED_MYSQL_INSERT
755 real_error = 2;
756 #else
757 real_error = mysql_errno(logsql_server_p);
758 #endif
759 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: first attempt failed, API said: error %d, \"%s\"", real_error, MYSQL_ERROR(logsql_server_p));
760 mysql_close(logsql_server_p);
761 logsql_server_p = NULL;
762 open_logdb_link(r->server);
763
764 if (logsql_server_p == NULL) { /* still unable to link */
765 signal(SIGPIPE, handler);
766 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: reconnect failed, unable to reach database. SQL logging stopped until child regains a db connection.");
767 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: log entries are being preserved in %s", cls->preserve_file);
768 return 1;
769 } else
770 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: db reconnect successful");
771
772 /* First sleep for a tiny amount of time. */
773 delay.tv_sec = 0;
774 delay.tv_nsec = 250000000; /* max is 999999999 (nine nines) */
775 ret = nanosleep(&delay, &remainder);
776 if (ret && errno != EINTR)
777 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: nanosleep unsuccessful");
778
779 /* Then make our second attempt */
780 retval = mysql_query(logsql_server_p,query);
781
782 /* If this one also failed, log that and append to our local offline file */
783 if (retval) {
784 #ifdef WANT_DELAYED_MYSQL_INSERT
785 real_error = 2;
786 #else
787 real_error = mysql_errno(logsql_server_p);
788 #endif
789 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: second attempt failed, API said: error %d, \"%s\" -- preserving", real_error, MYSQL_ERROR(logsql_server_p));
790 preserve_entry(r, query);
791 retval = real_error;
792 } else
793 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: second attempt successful");
794
795 /* Restore SIGPIPE to its original handler function */
796 signal(SIGPIPE, handler);
797 return retval;
798}
799
800/*-----------------------------------------------------*/
801/* safe_create_tables: create SQL table set for the */
802/* virtual server represented by cls. */
803/* */
804/* Parms: virtserver structure, request record */
805/* Returns: 0 on no errors */
806/* mysql error code on failure */
807/*-----------------------------------------------------*/
808int safe_create_tables(logsql_state *cls, request_rec *r)
809{
810 int retval;
811 unsigned int create_results;
812 char *create_access = NULL;
813 char *create_notes = NULL;
814 char *create_hout = NULL;
815 char *create_hin = NULL;
816 char *create_cookies = NULL;
817
818 char *createprefix = "create table if not exists `";
819 char *access_suffix =
820 "` (id char(19),\
821 agent varchar(255),\
822 bytes_sent int unsigned,\
823 child_pid smallint unsigned,\
824 cookie varchar(255),\
825 machine_id varchar(25),\
826 request_file varchar(255),\
827 referer varchar(255),\
828 remote_host varchar(50),\
829 remote_logname varchar(50),\
830 remote_user varchar(50),\
831 request_duration smallint unsigned,\
832 request_line varchar(255),\
833 request_method varchar(10),\
834 request_protocol varchar(10),\
835 request_time char(28),\
836 request_uri varchar(50),\
837 request_args varchar(255),\
838 server_port smallint unsigned,\
839 ssl_cipher varchar(25),\
840 ssl_keysize smallint unsigned,\
841 ssl_maxkeysize smallint unsigned,\
842 status smallint unsigned,\
843 time_stamp int unsigned,\
844 virtual_host varchar(50))";
845
846 char *notes_suffix =
847 "` (id char(19),\
848 item varchar(80),\
849 val varchar(80))";
850
851 char *headers_suffix =
852 "` (id char(19),\
853 item varchar(80),\
854 val varchar(80))";
855
856 char *cookies_suffix =
857 "` (id char(19),\
858 item varchar(80),\
859 val varchar(80))";
860
861 /* Find memory long enough to hold the whole CREATE string + \0 */
862 create_access = ap_pstrcat(r->pool, createprefix, cls->transfer_table_name, access_suffix, NULL);
863 create_notes = ap_pstrcat(r->pool, createprefix, cls->notes_table_name, notes_suffix, NULL);
864 create_hout = ap_pstrcat(r->pool, createprefix, cls->hout_table_name, headers_suffix, NULL);
865 create_hin = ap_pstrcat(r->pool, createprefix, cls->hin_table_name, headers_suffix, NULL);
866 create_cookies= ap_pstrcat(r->pool, createprefix, cls->cookie_table_name, cookies_suffix, NULL);
867
868 #ifdef DEBUG
869 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: create string: %s", create_access);
870 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: create string: %s", create_notes);
871 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: create string: %s", create_hout);
872 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: create string: %s", create_hin);
873 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: create string: %s", create_cookies);
874 #endif
875
876 /* Assume that things worked unless told otherwise */
877 retval = 0;
878
879 if ((create_results = safe_sql_query(r, create_access))) {
880 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: failed to create access table");
881 retval = create_results;
882 }
883
884 if ((create_results = safe_sql_query(r, create_notes))) {
885 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: failed to create notes table");
886 retval = create_results;
887 }
888
889 if ((create_results = safe_sql_query(r, create_hin))) {
890 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: failed to create header_out table");
891 retval = create_results;
892 }
893
894 if ((create_results = safe_sql_query(r, create_hout))) {
895 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: failed to create header_in table");
896 retval = create_results;
897 }
898
899 if ((create_results = safe_sql_query(r, create_cookies))) {
900 ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"mod_log_sql: failed to create cookies table");
901 retval = create_results;
902 }
903
904 return retval;
905}
906
907/* ------------------------------------------------*
908 * Command handlers that are called according *
909 * to the directives found at Apache runtime. *
910 * ------------------------------------------------*/
911
912const char *set_log_sql_massvirtual(cmd_parms *parms, void *dummy, int flag)
913{
914 logsql_massvirtual = ( flag ? 1 : 0);
915 return NULL;
916}
917
918const char *set_log_sql_force_preserve(cmd_parms *parms, void *dummy, int flag)
919{
920 logsql_forcepreserve = ( flag ? 1 : 0);
921 return NULL;
922}
923
924const char *set_log_sql_machine_id(cmd_parms *parms, void *dummy, char *arg)
925{
926 logsql_machid = arg;
927 return NULL;
928}
929
930const char *set_log_sql_create(cmd_parms *parms, void *dummy, int flag)
931{
932 if (logsql_massvirtual)
933 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLCreateTables when LogSQLMassVirtualHosting is On. Ignoring.");
934 else
935 logsql_createtables = ( flag ? 1 : 0);
936 return NULL;
937}
938
939const char *set_log_sql_db(cmd_parms *parms, void *dummy, char *arg)
940{
941 logsql_dbname = arg;
942 return NULL;
943}
944
945const char *set_log_sql_cookie(cmd_parms *parms, void *dummy, char *arg)
946{
947 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
948
949 cls->cookie_name = arg;
950 return NULL;
951}
952
953const char *set_log_sql_preserve_file(cmd_parms *parms, void *dummy, char *arg)
954{
955 /* char *pfile; */
956 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
957
958 /* pfile = ap_pstrcat(parms->pool, "/tmp/", arg, NULL); */
959 cls->preserve_file = arg;
960 return NULL;
961}
962
963const char *set_log_sql_info(cmd_parms *parms, void *dummy, char *host, char *user, char *pwd)
964{
965 if (*host != '.') {
966 logsql_dbhost = host;
967 }
968 if (*user != '.') {
969 logsql_dbuser = user;
970 }
971 if (*pwd != '.') {
972 logsql_dbpwd = pwd;
973 }
974 return NULL;
975}
976
977const char *set_log_sql_transfer_table(cmd_parms *parms, void *dummy, char *arg)
978{
979 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
980
981 if (logsql_massvirtual != 0)
982 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLTransferLogTable when LogSQLMassVirtualHosting is On. Ignoring.");
983 else
984 cls->transfer_table_name = arg;
985 return NULL;
986}
987
988const char *set_log_sql_cookie_table(cmd_parms *parms, void *dummy, char *arg)
989{
990 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
991
992 if (logsql_massvirtual != 0)
993 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLCookieLogTable when LogSQLMassVirtualHosting is On. Ignoring.");
994 else
995 cls->cookie_table_name = arg;
996 return NULL;
997}
998
999const char *set_log_sql_notes_table(cmd_parms *parms, void *dummy, char *arg)
1000{
1001 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1002
1003 if (logsql_massvirtual != 0)
1004 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLNotesLogTable when LogSQLMassVirtualHosting is On. Ignoring.");
1005 else
1006 cls->notes_table_name = arg;
1007 return NULL;
1008}
1009
1010const char *set_log_sql_hin_table(cmd_parms *parms, void *dummy, char *arg)
1011{
1012 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1013
1014 if (logsql_massvirtual != 0)
1015 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLHeadersInLogTable when LogSQLMassVirtualHosting is On. Ignoring.");
1016 else
1017 cls->hin_table_name = arg;
1018 return NULL;
1019}
1020
1021const char *set_log_sql_hout_table(cmd_parms *parms, void *dummy, char *arg)
1022{
1023 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1024
1025 if (logsql_massvirtual != 0)
1026 ap_log_error(APLOG_MARK,WARNINGLEVEL,parms->server,"mod_log_sql: do not set LogSQLHeadersOutLogTable when LogSQLMassVirtualHosting is On. Ignoring.");
1027 else
1028 cls->hout_table_name = arg;
1029 return NULL;
1030}
1031
1032const char *set_log_sql_transfer_log_format(cmd_parms *parms, void *dummy, char *arg)
1033{
1034 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1035
1036 cls->transfer_log_format = arg;
1037 return NULL;
1038}
1039
1040const char *set_log_sql_socket_file(cmd_parms *parms, void *dummy, char *arg)
1041{
1042 logsql_socketfile = arg;
1043
1044 return NULL;
1045}
1046
1047const char *set_log_sql_tcp_port(cmd_parms *parms, void *dummy, char *arg)
1048{
1049 logsql_tcpport = (unsigned int)atoi(arg);
1050
1051 return NULL;
1052}
1053
1054const char *add_log_sql_transfer_accept(cmd_parms *parms, void *dummy, char *arg)
1055{
1056 char **addme;
1057 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1058
1059 addme = push_array(cls->transfer_accept_list);
1060 *addme = pstrdup(cls->transfer_accept_list->pool, arg);
1061 return NULL;
1062}
1063
1064const char *add_log_sql_transfer_ignore(cmd_parms *parms, void *dummy, char *arg)
1065{
1066 char **addme;
1067 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1068
1069 addme = push_array(cls->transfer_ignore_list);
1070 *addme = pstrdup(cls->transfer_ignore_list->pool, arg);
1071 return NULL;
1072}
1073
1074const char *add_log_sql_remhost_ignore(cmd_parms *parms, void *dummy, char *arg)
1075{
1076 char **addme;
1077 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1078
1079 addme = push_array(cls->remhost_ignore_list);
1080 *addme = pstrdup(cls->remhost_ignore_list->pool, arg);
1081 return NULL;
1082}
1083
1084const char *add_log_sql_note(cmd_parms *parms, void *dummy, char *arg)
1085{
1086 char **addme;
1087 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1088
1089 addme = push_array(cls->notes_list);
1090 *addme = pstrdup(cls->notes_list->pool, arg);
1091 return NULL;
1092}
1093
1094const char *add_log_sql_hout(cmd_parms *parms, void *dummy, char *arg)
1095{
1096 char **addme;
1097 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1098
1099 addme = push_array(cls->hout_list);
1100 *addme = pstrdup(cls->hout_list->pool, arg);
1101 return NULL;
1102}
1103
1104const char *add_log_sql_hin(cmd_parms *parms, void *dummy, char *arg)
1105{
1106 char **addme;
1107 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1108
1109 addme = push_array(cls->hin_list);
1110 *addme = pstrdup(cls->hin_list->pool, arg);
1111 return NULL;
1112}
1113
1114const char *add_log_sql_cookie(cmd_parms *parms, void *dummy, char *arg)
1115{
1116 char **addme;
1117 logsql_state *cls = get_module_config(parms->server->module_config, &sql_log_module);
1118
1119 addme = push_array(cls->cookie_list);
1120 *addme = pstrdup(cls->cookie_list->pool, arg);
1121 return NULL;
1122}
1123
1124
1125
1126/*------------------------------------------------------------*
1127 * Apache-specific hooks into the module code *
1128 * that are defined in the array 'mysql_lgog_module' (at EOF) *
1129 *------------------------------------------------------------*/
1130
1131
1132/*
1133 * This function is called during server initialisation when an heavy-weight
1134 * process (such as a child) is being initialised. As with the
1135 * module-initialisation function, any information that needs to be recorded
1136 * must be in static cells, since there's no configuration record.
1137 *
1138 * There is no return value.
1139 */
1140static void log_sql_child_init(server_rec *s, pool *p)
1141{
1142 int retval;
1143
1144 /* Open a link to the database */
1145 retval = open_logdb_link(s);
1146 if (!retval)
1147 ap_log_error(APLOG_MARK,ERRLEVEL,s,"mod_log_sql: child spawned but unable to open database link");
1148
1149 #ifdef DEBUG
1150 if ( (retval == 1) || (retval == 2) )
1151 ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"mod_log_sql: open_logdb_link successful");
1152 if (retval == 3)
1153 ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"mod_log_sql: open_logdb_link said that preservation is forced");
1154 #endif
1155}
1156
1157/*
1158 * This function is called when an heavy-weight process (such as a child) is
1159 * being run down or destroyed. As with the child-initialisation function,
1160 * any information that needs to be recorded must be in static cells, since
1161 * there's no configuration record.
1162 *
1163 * There is no return value.
1164 */
1165static void log_sql_child_exit(server_rec *s, pool *p)
1166{
1167 mysql_close(logsql_server_p);
1168}
1169
1170
1171/*
1172void *log_sql_initializer(server_rec *main_server, pool *p)
1173{
1174 server_rec *s;
1175
1176 logsql_state main_conf = ap_get_module_config(main_server->module_config, &sql_log_module);
1177
1178 for (server_rec *s = main_server; s; s = s->next) {
1179 conf = ap_get_module_config(s->module_config, &sql_log_module);
1180 if (conf->transfer_log_format == NULL && s != main_server) {
1181 *conf = *main_conf;
1182 }
1183
1184}
1185 */
1186
1187/*
1188 * This function gets called to create a per-server configuration
1189 * record. It will always be called for the main server and
1190 * for each virtual server that is established. Each server maintains
1191 * its own state that is separate from the others' states.
1192 *
1193 * The return value is a pointer to the created module-specific
1194 * structure.
1195 */
1196void *log_sql_make_state(pool *p, server_rec *s)
1197{
1198
1199 logsql_state *cls = (logsql_state *) ap_palloc(p, sizeof(logsql_state));
1200
1201 /* These defaults are overridable in the httpd.conf file. */
1202 cls->transfer_table_name = NULL; /* No default b/c we want its absence to disable logging */
1203 cls->transfer_log_format = "AbHhmRSsTUuv";
1204 cls->notes_table_name = "notes";
1205 cls->hin_table_name = "headers_in";
1206 cls->hout_table_name = "headers_out";
1207 cls->cookie_table_name = "cookies";
1208 cls->preserve_file = "/tmp/sql-preserve";
1209
1210 cls->transfer_ignore_list = make_array(p, 1, sizeof(char *));
1211 cls->transfer_accept_list = make_array(p, 1, sizeof(char *));
1212 cls->remhost_ignore_list = make_array(p, 1, sizeof(char *));
1213 cls->notes_list = make_array(p, 1, sizeof(char *));
1214 cls->hin_list = make_array(p, 1, sizeof(char *));
1215 cls->hout_list = make_array(p, 1, sizeof(char *));
1216 cls->cookie_list = make_array(p, 1, sizeof(char *));
1217 cls->cookie_name = NULL;
1218
1219 return (void *) cls;
1220}
1221
1222
1223/* Setup of the available httpd.conf configuration commands.
1224 * Structure: command, function called, NULL, where available, how many arguments, verbose description
1225 */
1226command_rec log_sql_cmds[] = {
1227 {"LogSQLTransferLogTable", set_log_sql_transfer_table, NULL, RSRC_CONF, TAKE1,
1228 "The database table that holds the transfer log"}
1229 ,
1230 {"LogSQLNotesLogTable", set_log_sql_notes_table, NULL, RSRC_CONF, TAKE1,
1231 "The database table that holds the notes"}
1232 ,
1233 {"LogSQLHeadersOutLogTable", set_log_sql_hout_table, NULL, RSRC_CONF, TAKE1,
1234 "The database table that holds the outbound headers"}
1235 ,
1236 {"LogSQLHeadersInLogTable", set_log_sql_hin_table, NULL, RSRC_CONF, TAKE1,
1237 "The database table that holds the inbound headers"}
1238 ,
1239 {"LogSQLCookieLogTable", set_log_sql_cookie_table, NULL, RSRC_CONF, TAKE1,
1240 "The database table that holds the cookie info"}
1241 ,
1242 {"LogSQLTransferLogFormat", set_log_sql_transfer_log_format, NULL, RSRC_CONF, TAKE1,
1243 "Instruct the module what information to log to the database transfer log"}
1244 ,
1245 {"LogSQLMachineID", set_log_sql_machine_id, NULL, RSRC_CONF, TAKE1,
1246 "Machine ID that the module will log, useful in web clusters to differentiate machines"}
1247 ,
1248 {"LogSQLRequestAccept", add_log_sql_transfer_accept, NULL, RSRC_CONF, ITERATE,
1249 "List of URIs to accept for logging. Accesses that don't match will not be logged"}
1250 ,
1251 {"LogSQLRequestIgnore", add_log_sql_transfer_ignore, NULL, RSRC_CONF, ITERATE,
1252 "List of URIs to ignore. Accesses that match will not be logged to database"}
1253 ,
1254 {"LogSQLRemhostIgnore", add_log_sql_remhost_ignore, NULL, RSRC_CONF, ITERATE,
1255 "List of remote hosts to ignore. Accesses that match will not be logged to database"}
1256 ,
1257 {"LogSQLDatabase", set_log_sql_db, NULL, RSRC_CONF, TAKE1,
1258 "The name of the database database for logging"}
1259 ,
1260 {"LogSQLWhichCookie", set_log_sql_cookie, NULL, RSRC_CONF, TAKE1,
1261 "The single cookie that you want logged in the access_log when using the 'c' config directive"}
1262 ,
1263 {"LogSQLLoginInfo", set_log_sql_info, NULL, RSRC_CONF, TAKE3,
1264 "The database host, user-id and password for logging"}
1265 ,
1266 {"LogSQLCreateTables", set_log_sql_create, NULL, RSRC_CONF, FLAG,
1267 "Turn on module's capability to create its SQL tables on the fly"}
1268 ,
1269 {"LogSQLMassVirtualHosting", set_log_sql_massvirtual, NULL, RSRC_CONF, FLAG,
1270 "Activates option(s) useful for ISPs performing mass virutal hosting"}
1271 ,
1272 {"LogSQLForcePreserve", set_log_sql_force_preserve, NULL, RSRC_CONF, FLAG,
1273 "Forces logging to preserve file and bypasses database"}
1274 ,
1275 {"LogSQLPreserveFile", set_log_sql_preserve_file, NULL, RSRC_CONF, TAKE1,
1276 "Name of the file to use for data preservation during database downtime"}
1277 ,
1278 {"LogSQLSocketFile", set_log_sql_socket_file, NULL, RSRC_CONF, TAKE1,
1279 "Name of the file to employ for socket connections to database"}
1280 ,
1281 {"LogSQLTCPPort", set_log_sql_tcp_port, NULL, RSRC_CONF, TAKE1,
1282 "Port number to use for TCP connections to database, defaults to 3306 if not set"}
1283 ,
1284 {"LogSQLWhichNotes", add_log_sql_note, NULL, RSRC_CONF, ITERATE,
1285 "Notes that you would like to log in a separate table"}
1286 ,
1287 {"LogSQLWhichHeadersOut", add_log_sql_hout, NULL, RSRC_CONF, ITERATE,
1288 "Outbound headers that you would like to log in a separate table"}
1289 ,
1290 {"LogSQLWhichHeadersIn", add_log_sql_hin, NULL, RSRC_CONF, ITERATE,
1291 "Inbound headers that you would like to log in a separate table"}
1292 ,
1293 {"LogSQLWhichCookies", add_log_sql_cookie, NULL, RSRC_CONF, ITERATE,
1294 "The cookie(s) that you would like to log in a separate table"}
1295 ,
1296 {NULL}
1297};
1298
1299
1300
1301/* Routine to perform the actual construction and execution of the relevant
1302 * INSERT statements.
1303 */
1304int log_sql_transaction(request_rec *orig)
1305{
1306 char **ptrptr, **ptrptr2;
1307 logsql_state *cls = get_module_config(orig->server->module_config, &sql_log_module);
1308 const char *access_query;
1309 request_rec *r;
1310
1311 /* We handle mass virtual hosting differently. Dynamically determine the name
1312 * of the table from the virtual server's name, and flag it for creation.
1313 */
1314 if (logsql_massvirtual) {
1315 char *access_base = "access_";
1316 char *notes_base = "notes_";
1317 char *hout_base = "headout_";
1318 char *hin_base = "headin_";
1319 char *cookie_base = "cookies_";
1320 char *a_tablename;
1321 char *n_tablename;
1322 char *i_tablename;
1323 char *o_tablename;
1324 char *c_tablename;
1325 unsigned int i;
1326
1327 /* Find memory long enough to hold the table name + \0. */
1328 a_tablename = ap_pstrcat(orig->pool, access_base, ap_get_server_name(orig), NULL);
1329 n_tablename = ap_pstrcat(orig->pool, notes_base, ap_get_server_name(orig), NULL);
1330 i_tablename = ap_pstrcat(orig->pool, hin_base, ap_get_server_name(orig), NULL);
1331 o_tablename = ap_pstrcat(orig->pool, hout_base, ap_get_server_name(orig), NULL);
1332 c_tablename = ap_pstrcat(orig->pool, cookie_base, ap_get_server_name(orig), NULL);
1333
1334 /* Transform any dots to underscores */
1335 for (i = 0; i < strlen(a_tablename); i++) {
1336 if (a_tablename[i] == '.')
1337 a_tablename[i] = '_';
1338 }
1339 for (i = 0; i < strlen(n_tablename); i++) {
1340 if (n_tablename[i] == '.')
1341 n_tablename[i] = '_';
1342 }
1343 for (i = 0; i < strlen(i_tablename); i++) {
1344 if (i_tablename[i] == '.')
1345 i_tablename[i] = '_';
1346 }
1347 for (i = 0; i < strlen(o_tablename); i++) {
1348 if (o_tablename[i] == '.')
1349 o_tablename[i] = '_';
1350 }
1351 for (i = 0; i < strlen(c_tablename); i++) {
1352 if (c_tablename[i] == '.')
1353 c_tablename[i] = '_';
1354 }
1355
1356 /* Tell this virtual server its transfer table name, and
1357 * turn on create_tables, which is implied by massvirtual.
1358 */
1359 cls->transfer_table_name = a_tablename;
1360 cls->notes_table_name = n_tablename;
1361 cls->hout_table_name = o_tablename;
1362 cls->hin_table_name = i_tablename;
1363 cls->cookie_table_name = c_tablename;
1364 logsql_createtables = 1;
1365 }
1366
1367 /* Do we have enough info to log? */
1368 if (!cls->transfer_table_name) {
1369 return DECLINED;
1370 } else {
1371 const char *thehost;
1372 const char *theitem;
1373 char *fields = "", *values = "";
1374 char *itemsets = "";
1375 char *note_query = NULL;
1376 char *hin_query = NULL;
1377 char *hout_query = NULL;
1378 char *cookie_query = NULL;
1379 const char *unique_id;
1380 const char *formatted_item;
1381 int i, j, length;
1382 int proceed;
1383
1384 for (r = orig; r->next; r = r->next) {
1385 continue;
1386 }
1387
1388 /* The following is a stolen upsetting mess of pointers, I'm sorry.
1389 * Anyone with the motiviation and/or the time should feel free
1390 * to make this cleaner. :) */
1391 ptrptr2 = (char **) (cls->transfer_accept_list->elts + (cls->transfer_accept_list->nelts * cls->transfer_accept_list->elt_size));
1392
1393 /* Go through each element of the accept list and compare it to the
1394 * request_uri. If we don't get a match, return without logging */
1395 if ((r->uri) && (cls->transfer_accept_list->nelts)) {
1396 proceed = 0;
1397 for (ptrptr = (char **) cls->transfer_accept_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->transfer_accept_list->elt_size))
1398 if (strstr(r->uri, *ptrptr)) {
1399 proceed = 1;
1400 break;
1401 }
1402 if (!proceed)
1403 return OK;
1404 }
1405
1406 /* Go through each element of the ignore list and compare it to the
1407 * request_uri. If we get a match, return without logging */
1408 ptrptr2 = (char **) (cls->transfer_ignore_list->elts + (cls->transfer_ignore_list->nelts * cls->transfer_ignore_list->elt_size));
1409 if (r->uri) {
1410 for (ptrptr = (char **) cls->transfer_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->transfer_ignore_list->elt_size))
1411 if (strstr(r->uri, *ptrptr)) {
1412 return OK;
1413 }
1414 }
1415
1416 /* Go through each element of the ignore list and compare it to the
1417 * remote host. If we get a match, return without logging */
1418 ptrptr2 = (char **) (cls->remhost_ignore_list->elts + (cls->remhost_ignore_list->nelts * cls->remhost_ignore_list->elt_size));
1419 thehost = get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME);
1420 if (thehost) {
1421 for (ptrptr = (char **) cls->remhost_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->remhost_ignore_list->elt_size))
1422 if (strstr(thehost, *ptrptr)) {
1423 return OK;
1424 }
1425 }
1426
1427 length = strlen(cls->transfer_log_format);
1428
1429 /* Iterate through the format characters and set up the INSERT string according to
1430 * what the user has configured. */
1431 for (i = 0; i < length; i++) {
1432 j = 0;
1433
1434 while (log_sql_item_keys[j].ch) {
1435
1436 if (log_sql_item_keys[j].ch == cls->transfer_log_format[i]) {
1437 /* Yes, this key is one of the configured keys.
1438 * Call the key's function and put the returned value into 'formatted_item' */
1439 formatted_item = log_sql_item_keys[j].func(log_sql_item_keys[j].want_orig_default ? orig : r, "");
1440
1441 /* Massage 'formatted_item' for proper SQL eligibility... */
1442 if (!formatted_item) {
1443 formatted_item = "";
1444 } else if (formatted_item[0] == '-' && formatted_item[1] == '\0' && !log_sql_item_keys[j].string_contents) {
1445 /* If apache tried to log a '-' character for a numeric field, convert that to a zero
1446 * because the database expects a numeral and will reject the '-' character. */
1447 formatted_item = "0";
1448 }
1449
1450 /* Append the fieldname and value-to-insert to the appropriate strings, quoting stringvals with ' as appropriate */
1451 fields = pstrcat(r->pool, fields, (i > 0 ? "," : ""),
1452 log_sql_item_keys[j].sql_field_name, NULL);
1453
1454 values = pstrcat(r->pool, values, (i > 0 ? "," : ""),
1455 (log_sql_item_keys[j].string_contents ? "'" : ""),
1456 escape_query(formatted_item, r->pool),
1457 (log_sql_item_keys[j].string_contents ? "'" : ""), NULL);
1458 break;
1459 }
1460 j++;
1461
1462 }
1463 }
1464
1465 /* Work through the list of notes defined by LogSQLWhichNotes */
1466 i = 0;
1467 unique_id = extract_unique_id(r, "");
1468
1469 ptrptr2 = (char **) (cls->notes_list->elts + (cls->notes_list->nelts * cls->notes_list->elt_size));
1470 for (ptrptr = (char **) cls->notes_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->notes_list->elt_size)) {
1471 /* If the specified note (*ptrptr) exists for the current request... */
1472 if ((theitem = ap_table_get(r->notes, *ptrptr))) {
1473 itemsets = ap_pstrcat(r->pool, itemsets,
1474 (i > 0 ? "," : ""),
1475 "('",
1476 unique_id,
1477 "','",
1478 escape_query(*ptrptr, r->pool),
1479 "','",
1480 escape_query(theitem, r->pool),
1481 "')",
1482 NULL);
1483 i++;
1484 }
1485 }
1486 if ( itemsets != "" ) {
1487 note_query = ap_pstrcat(r->pool, logsql_insertclause, "`", cls->notes_table_name, "` (id, item, val) values ", itemsets, NULL);
1488 #ifdef DEBUG
1489 ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"mod_log_sql: note string: %s", note_query);
1490 #endif
1491 }
1492
1493 /* Work through the list of headers-out defined by LogSQLWhichHeadersOut*/
1494 i = 0;
1495 itemsets = "";
1496
1497 ptrptr2 = (char **) (cls->hout_list->elts + (cls->hout_list->nelts * cls->hout_list->elt_size));
1498 for (ptrptr = (char **) cls->hout_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->hout_list->elt_size)) {
1499 /* If the specified header (*ptrptr) exists for the current request... */
1500 if ((theitem = ap_table_get(r->headers_out, *ptrptr))) {
1501 itemsets = ap_pstrcat(r->pool, itemsets,
1502 (i > 0 ? "," : ""),
1503 "('",
1504 unique_id,
1505 "','",
1506 escape_query(*ptrptr, r->pool),
1507 "','",
1508 escape_query(theitem, r->pool),
1509 "')",
1510 NULL);
1511 i++;
1512 }
1513 }
1514 if ( itemsets != "" ) {
1515 hout_query = ap_pstrcat(r->pool, logsql_insertclause, "`", cls->hout_table_name, "` (id, item, val) values ", itemsets, NULL);
1516 #ifdef DEBUG
1517 ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"mod_log_sql: header_out string: %s", hout_query);
1518 #endif
1519 }
1520
1521
1522 /* Work through the list of headers-in defined by LogSQLWhichHeadersIn */
1523 i = 0;
1524 itemsets = "";
1525
1526 ptrptr2 = (char **) (cls->hin_list->elts + (cls->hin_list->nelts * cls->hin_list->elt_size));
1527 for (ptrptr = (char **) cls->hin_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->hin_list->elt_size)) {
1528 /* If the specified header (*ptrptr) exists for the current request... */
1529 if ((theitem = ap_table_get(r->headers_in, *ptrptr))) {
1530 itemsets = ap_pstrcat(r->pool, itemsets,
1531 (i > 0 ? "," : ""),
1532 "('",
1533 unique_id,
1534 "','",
1535 escape_query(*ptrptr, r->pool),
1536 "','",
1537 escape_query(theitem, r->pool),
1538 "')",
1539 NULL);
1540 i++;
1541 }
1542 }
1543 if ( itemsets != "" ) {
1544 hin_query = ap_pstrcat(r->pool, logsql_insertclause, "`", cls->hin_table_name, "` (id, item, val) values ", itemsets, NULL);
1545 #ifdef DEBUG
1546 ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"mod_log_sql: header_in string: %s", hin_query);
1547 #endif
1548 }
1549
1550
1551 /* Work through the list of cookies defined by LogSQLWhichCookies */
1552 i = 0;
1553 itemsets = "";
1554
1555 ptrptr2 = (char **) (cls->cookie_list->elts + (cls->cookie_list->nelts * cls->cookie_list->elt_size));
1556 for (ptrptr = (char **) cls->cookie_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->cookie_list->elt_size)) {
1557 /* If the specified cookie (*ptrptr) exists for the current request... */
1558 if ( strncmp((theitem = extract_specific_cookie(r, *ptrptr)), "-", 1) ) {
1559 itemsets = ap_pstrcat(r->pool, itemsets,
1560 (i > 0 ? "," : ""),
1561 "('",
1562 unique_id,
1563 "','",
1564 escape_query(*ptrptr, r->pool),
1565 "','",
1566 escape_query(theitem, r->pool),
1567 "')",
1568 NULL);
1569 i++;
1570 }
1571
1572 }
1573 if ( itemsets != "" ) {
1574 cookie_query = ap_pstrcat(r->pool, logsql_insertclause, "`", cls->cookie_table_name, "` (id, item, val) values ", itemsets, NULL);
1575 #ifdef DEBUG
1576 ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"mod_log_sql: cookie string: %s", cookie_query);
1577 #endif
1578 }
1579
1580
1581 /* Set up the actual INSERT statement */
1582 access_query = ap_pstrcat(r->pool, logsql_insertclause, "`", cls->transfer_table_name, "` (", fields, ") values (", values, ")", NULL);
1583
1584 #ifdef DEBUG
1585 ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_sql: access string: %s", access_query);
1586 #endif
1587
1588 /* If the person activated force-preserve, go ahead and push all the entries
1589 * into the preserve file, then return.
1590 */
1591 if (logsql_forcepreserve) {
1592 #ifdef DEBUG
1593 ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"mod_log_sql: preservation forced");
1594 #endif
1595 preserve_entry(orig, access_query);
1596 if ( note_query != NULL )
1597 preserve_entry(orig, note_query);
1598 if ( hin_query != NULL )
1599 preserve_entry(orig, hin_query);
1600 if ( hout_query != NULL )
1601 preserve_entry(orig, hout_query);
1602 if ( cookie_query != NULL )
1603 preserve_entry(orig, cookie_query);
1604 return OK;
1605 }
1606
1607 /* How's our mysql link integrity? */
1608 if (logsql_server_p == NULL) {
1609
1610 /* Make a try to establish the link */
1611 open_logdb_link(r->server);
1612
1613 if (logsql_server_p == NULL) {
1614 /* Unable to re-establish a DB link, so assume that it's really
1615 * gone and send the entry to the preserve file instead.
1616 * This short-circuits safe_sql_query() during a db outage and therefore
1617 * we don't keep logging the db error over and over.
1618 */
1619 preserve_entry(orig, access_query);
1620 if ( note_query != NULL )
1621 preserve_entry(orig, note_query);
1622 if ( hin_query != NULL )
1623 preserve_entry(orig, hin_query);
1624 if ( hout_query != NULL )
1625 preserve_entry(orig, hout_query);
1626 if ( cookie_query != NULL )
1627 preserve_entry(orig, cookie_query);
1628
1629 return OK;
1630 } else {
1631 /* Whew, we got the DB link back */
1632 ap_log_error(APLOG_MARK,NOTICELEVEL,orig->server,"mod_log_sql: child established database connection");
1633 }
1634 }
1635
1636
1637 /* ---> So as of here we have a non-null value of mysql_log. <--- */
1638 /* ---> i.e. we have a good MySQL connection. <--- */
1639
1640 /* Make the access-table insert */
1641 safe_sql_query(orig, access_query);
1642
1643 /* Log the optional notes, headers, etc. */
1644 if (note_query)
1645 safe_sql_query(orig, note_query);
1646
1647 if (hout_query)
1648 safe_sql_query(orig, hout_query);
1649
1650 if (hin_query)
1651 safe_sql_query(orig, hin_query);
1652
1653 if (cookie_query)
1654 safe_sql_query(orig, cookie_query);
1655
1656 return OK;
1657 }
1658}
1659
1660
1661
1662
1663/* The configuration array that sets up the hooks into the module. */
1664module sql_log_module = {
1665 STANDARD_MODULE_STUFF,
1666 NULL, /* module initializer */
1667 NULL, /* create per-dir config */
1668 NULL, /* merge per-dir config */
1669 log_sql_make_state, /* create server config */
1670 NULL, /* merge server config */
1671 log_sql_cmds, /* config directive table */
1672 NULL, /* [9] content handlers */
1673 NULL, /* [2] URI-to-filename translation */
1674 NULL, /* [5] check/validate user_id */
1675 NULL, /* [6] check authorization */
1676 NULL, /* [4] check access by host */
1677 NULL, /* [7] MIME type checker/setter */
1678 NULL, /* [8] fixups */
1679 log_sql_transaction, /* [10] logger */
1680 NULL /* [3] header parser */
1681#if MODULE_MAGIC_NUMBER >= 19970728 /* 1.3-dev or later support these additionals... */
1682 ,log_sql_child_init, /* child process initializer */
1683 log_sql_child_exit, /* process exit/cleanup */
1684 NULL /* [1] post read-request */
1685#endif
1686
1687};
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..5a5dd6a
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,168 @@
1# @configure_input@
2
3top_srcdir = @top_srcdir@
4srcdir = @abs_srcdir@
5builddir = @abs_builddir@
6
7HEADERS = ../include/mod_log_sql.h \
8 functions.h \
9 functions13.h \
10 functions20.h \
11 ../include/apache13.h \
12 ../include/apache20.h \
13 ../include/winconfig.h
14
15CFLAGS = -Wc,-Wall -Wc,-fno-strict-aliasing -I$(top_srcdir)/include
16
17ifeq (@OOO_MAINTAIN@,1)
18CFLAGS += -Wc,-Werror
19endif
20
21coreSOURCES = @PACKAGE_NAME@.c
22coreTARGET = @PACKAGE_NAME@@APXS_EXTENSION@
23coreLDADD = @RT_LIBS@
24coreCFLAGS =
25coreNAME = log_sql
26TARGETS = $(coreTARGET)
27
28sslSOURCES = @PACKAGE_NAME@_ssl.c
29sslTARGET = @PACKAGE_NAME@_ssl@APXS_EXTENSION@
30sslLDADD =
31sslCFLAGS = @MOD_SSL_CFLAGS@
32sslNAME = log_sql_ssl
33
34ifeq (@WANT_SSL_MOD@,1)
35TARGETS += $(sslTARGET)
36endif
37
38logioSOURCES = @PACKAGE_NAME@_logio.c
39logioTARGET = @PACKAGE_NAME@_logio@APXS_EXTENSION@
40logioLDADD =
41logioCFLAGS =
42logioNAME = log_sql_logio
43
44ifeq (@WANT_LOGIO_MOD@,1)
45TARGETS += $(logioTARGET)
46endif
47
48mysqlSOURCES = @PACKAGE_NAME@_mysql.c
49mysqlTARGET = @PACKAGE_NAME@_mysql@APXS_EXTENSION@
50mysqlLDADD = @MYSQL_LDFLAGS@ @MYSQL_LIBS@
51mysqlCFLAGS = @MYSQL_CFLAGS@
52mysqlNAME = log_sql_mysql
53
54ifeq (@WANT_MYSQL_MOD@,1)
55TARGETS += $(mysqlTARGET)
56endif
57
58pgsqlSOURCES = @PACKAGE_NAME@_pgsql.c
59pgsqlTARGET = @PACKAGE_NAME@_pgsql@APXS_EXTENSION@
60pgsqlLDADD = @PGSQL_LDFLAGS@ @PGSQL_LIBS@
61pgsqlCFLAGS = @PGSQL_CFLAGS@
62pgsqlNAME = log_sql_pgsql
63
64ifeq (@WANT_PGSQL_MOD@,1)
65TARGETS += $(pgsqlTARGET)
66endif
67
68dbiSOURCES = @PACKAGE_NAME@_dbi.c
69dbiTARGET = @PACKAGE_NAME@_dbi@APXS_EXTENSION@
70dbiLDADD = @DBI_LDFLAGS@ @DBI_LIBS@
71dbiCFLAGS = @DBI_CFLAGS@
72dbiNAME = log_sql_dbi
73
74ifeq (@WANT_DBI_MOD@,1)
75TARGETS += $(dbiTARGET)
76endif
77
78dbdSOURCES = @PACKAGE_NAME@_dbd.c
79dbdTARGET = @PACKAGE_NAME@_dbd@APXS_EXTENSION@
80dbdLDADD =
81dbdCFLAGS =
82dbdNAME = log_sql_dbd
83
84ifeq (@WANT_DBD_MOD@,1)
85TARGETS += $(dbdTARGET)
86endif
87
88OBJ = $(coreSOURCES:.c=.o) $(logioSOURCES:.c=.o) $(sslSOURCES:.c=.o) $(mysqlSOURCES:.c=.o) \
89 $(dbiSOURCES:.c=.o) $(pgsqlSOURCES:.c=.o) $(dbdSOURCES:.c=.o)
90
91LO = $(coreSOURCES:.c=.lo) $(logioSOURCES:.c=.lo) $(sslSOURCES:.c=.lo) $(mysqlSOURCES:.c=.lo) \
92 $(dbiSOURCES:.c=.lo) $(pgsqlSOURCES:.c=.lo) $(dbdSOURCES:.c=.lo)
93
94SLO = $(coreSOURCES:.c=.slo) $(logioSOURCES:.c=.slo) $(sslSOURCES:.c=.slo) $(mysqlSOURCES:.c=.slo) \
95 $(dbiSOURCES:.c=.slo) $(pgsqlSOURCES:.c=.slo) $(dbdSOURCES:.c=.slo)
96
97STD_DIST = Makefile.in
98
99DISTFILES = $(STD_DIST) $(EXTRA_DIST) $(coreSOURCES) $(HEADERS) \
100 $(sslSOURCES) $(logioSOURCES) $(mysqlSOURCES) $(pgsqlSOURCES) $(dbiSOURCES) $(dbdSOURCES)
101
102all: $(TARGETS)
103
104$(coreTARGET): $(coreSOURCES) $(HEADERS)
105 @@APXS_BIN@ -c -o $(coreTARGET) $(coreCFLAGS) $(CFLAGS) \
106 @DEFS@ @AP_DEFS@ $(coreLDADD) $(coreSOURCES)
107
108$(logioTARGET): $(logioSOURCES) $(HEADERS)
109 @@APXS_BIN@ -c -o $(logioTARGET) $(logioCFLAGS) $(CFLAGS) \
110 @DEFS@ @AP_DEFS@ $(logioLDADD) $(logioSOURCES)
111
112$(sslTARGET): $(sslSOURCES) $(HEADERS)
113 @@APXS_BIN@ -c -o $(sslTARGET) $(sslCFLAGS) $(CFLAGS) \
114 @DEFS@ @AP_DEFS@ $(sslLDADD) $(sslSOURCES)
115
116$(mysqlTARGET): $(mysqlSOURCES) $(HEADERS)
117 @@APXS_BIN@ -c -o $(mysqlTARGET) $(mysqlCFLAGS) $(CFLAGS) \
118 @DEFS@ @AP_DEFS@ $(mysqlLDADD) $(mysqlSOURCES)
119
120$(pgsqlTARGET): $(pgsqlSOURCES) $(HEADERS)
121 @@APXS_BIN@ -c -o $(pgsqlTARGET) $(pgsqlCFLAGS) $(CFLAGS) \
122 @DEFS@ @AP_DEFS@ $(pgsqlLDADD) $(pgsqlSOURCES)
123
124$(dbiTARGET): $(dbiSOURCES) $(HEADERS)
125 @@APXS_BIN@ -c -o $(dbiTARGET) $(dbiCFLAGS) $(CFLAGS) \
126 @DEFS@ @AP_DEFS@ $(dbiLDADD) $(dbiSOURCES)
127
128$(dbdTARGET): $(dbdSOURCES) $(HEADERS)
129 @@APXS_BIN@ -c -o $(dbdTARGET) $(dbdCFLAGS) $(CFLAGS) \
130 @DEFS@ @AP_DEFS@ $(dbdLDADD) $(dbdSOURCES)
131
132install: $(TARGETS)
133 @@APXS_BIN@ -n $(coreNAME) -i $(coreTARGET); \
134 if test @WANT_MYSQL_MOD@ -eq 1; then \
135 @APXS_BIN@ -n $(mysqlNAME) -i $(mysqlTARGET); \
136 fi; \
137 if test @WANT_PGSQL_MOD@ -eq 1; then \
138 @APXS_BIN@ -n $(pgsqlNAME) -i $(pgsqlTARGET); \
139 fi; \
140 if test @WANT_DBI_MOD@ -eq 1; then \
141 @APXS_BIN@ -n $(dbiNAME) -i $(dbiTARGET); \
142 fi; \
143 if test @WANT_DBD_MOD@ -eq 1; then \
144 @APXS_BIN@ -n $(dbdNAME) -i $(dbdTARGET); \
145 fi; \
146 if test @WANT_SSL_MOD@ -eq 1; then \
147 @APXS_BIN@ -n $(sslNAME) -i $(sslTARGET); \
148 fi; \
149 if test @WANT_LOGIO_MOD@ -eq 1; then \
150 @APXS_BIN@ -n $(logioNAME) -i $(logioTARGET); \
151 fi;
152
153activate:
154 @@APXS_BIN@ -n $(coreNAME) -i -a $(coreTARGET); \
155 if test @WANT_SSL_MOD@ -eq 1; then \
156 @APXS_BIN@ -n $(sslNAME) -i -a $(sslTARGET); \
157 fi
158
159clean:
160 $(RM) $(OBJ) $(SLO) $(LO) $(TARGETS)
161 $(RM) -r .libs
162
163local-dist: $(DISTFILES)
164 mkdir -p $(DESTDIR)
165 cp -dp --parents $(DISTFILES) $(DESTDIR)
166
167Makefile: Makefile.in ../config.status
168 cd .. && ./config.status
diff --git a/src/functions.h b/src/functions.h
new file mode 100644
index 0000000..07711d2
--- /dev/null
+++ b/src/functions.h
@@ -0,0 +1,261 @@
1/* $Id$ */
2
3/* Begin the individual functions that, given a request r,
4 * extract the needed information from it and return the
5 * value to the calling entity.
6 */
7
8static const char *extract_remote_host(request_rec *r, char *a)
9{
10 return (char *) ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL);
11}
12
13static const char *extract_remote_address(request_rec *r, char *a) __attribute__((unused));
14
15static const char *extract_remote_address(request_rec *r, char *a)
16{
17 return r->connection->remote_ip;
18}
19
20static const char *extract_local_address(request_rec *r, char *a) __attribute__((unused));
21
22static const char *extract_local_address(request_rec *r, char *a)
23{
24 return r->connection->local_ip;
25}
26
27static const char *extract_remote_logname(request_rec *r, char *a)
28{
29 return (char *) ap_get_remote_logname(r);
30}
31
32static const char *extract_remote_user(request_rec *r, char *a)
33{
34 #ifdef WITH_APACHE13
35 char *rvalue = r->connection->user;
36 #else
37 char *rvalue = r->user;
38 #endif
39 if (rvalue == NULL) {
40 rvalue = "-";
41 } else if (strlen(rvalue) == 0) {
42 rvalue = "\"\"";
43 }
44 return rvalue;
45}
46
47static const char *extract_request_line(request_rec *r, char *a)
48{
49 /* Upddated to mod_log_config logic */
50 /* NOTE: If the original request contained a password, we
51 * re-write the request line here to contain XXXXXX instead:
52 * (note the truncation before the protocol string for HTTP/0.9 requests)
53 * (note also that r->the_request contains the unmodified request)
54 */
55 return (r->parsed_uri.password)
56 ? apr_pstrcat(r->pool, r->method, " ",
57 apr_uri_unparse(r->pool,
58 &r->parsed_uri, 0),
59 r->assbackwards ? NULL : " ",
60 r->protocol, NULL)
61 : r->the_request;
62}
63
64static const char *extract_request_file(request_rec *r, char *a)
65{
66 return r->filename;
67}
68
69static const char *extract_request_uri(request_rec *r, char *a)
70{
71 return r->uri;
72}
73
74static const char *extract_request_method(request_rec *r, char *a)
75{
76 return r->method;
77}
78
79static const char *extract_request_protocol(request_rec *r, char *a)
80{
81 return r->protocol;
82}
83
84static const char *extract_request_query(request_rec *r, char *a)
85{
86 return (r->args) ? apr_pstrcat(r->pool, "?",
87 r->args, NULL)
88 : "";
89}
90
91static const char *extract_status(request_rec *r, char *a)
92{
93 if (r->status <= 0) {
94 return "-";
95 } else {
96 return apr_psprintf(r->pool, "%d", r->status);
97 }
98}
99
100static const char *extract_virtual_host(request_rec *r, char *a)
101{
102 return r->server->server_hostname;
103}
104
105static const char *extract_server_name(request_rec *r, char *a)
106{
107 return ap_get_server_name(r);
108}
109
110static const char *extract_machine_id(request_rec *r, char *a)
111{
112 if (!global_config.machid)
113 return "-";
114 else
115 return global_config.machid;
116}
117
118static const char *extract_server_port(request_rec *r, char *a)
119{
120 return apr_psprintf(r->pool, "%u",
121 r->server->port ? r->server->port : ap_default_port(r));
122}
123
124/* This respects the setting of UseCanonicalName so that
125 * the dynamic mass virtual hosting trick works better.
126 */
127static const char *log_server_name(request_rec *r, char *a) __attribute__((unused));
128static const char *log_server_name(request_rec *r, char *a)
129{
130 return ap_get_server_name(r);
131}
132
133static const char *extract_child_pid(request_rec *r, char *a)
134{
135 if (*a == '\0' || !strcmp(a, "pid")) {
136 return apr_psprintf(r->pool, "%" APR_PID_T_FMT, getpid());
137 }
138 else if (!strcmp(a, "tid")) {
139#if APR_HAS_THREADS
140 apr_os_thread_t tid = apr_os_thread_current();
141#else
142 int tid = 0; /* APR will format "0" anyway but an arg is needed */
143#endif
144 return apr_psprintf(r->pool, "%pT", &tid);
145 }
146 /* bogus format */
147 return a;
148}
149
150static const char *extract_referer(request_rec *r, char *a)
151{
152 const char *tempref;
153
154 tempref = apr_table_get(r->headers_in, "Referer");
155 if (!tempref)
156 {
157 return "-";
158 } else {
159 return tempref;
160 }
161}
162
163static const char *extract_agent(request_rec *r, char *a)
164{
165 const char *tempag;
166
167 tempag = apr_table_get(r->headers_in, "User-Agent");
168 if (!tempag)
169 {
170 return "-";
171 } else {
172 return tempag;
173 }
174}
175
176static const char *extract_specific_cookie(request_rec *r, char *a)
177{
178 const char *cookiestr;
179 char *cookieend;
180 char *isvalid;
181 char *cookiebuf;
182
183 if (a != NULL) {
184 log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server,
185 "watching for cookie '%s'", a);
186
187 /* Fetch out the cookie header */
188 cookiestr = (char *)apr_table_get(r->headers_in, "cookie2");
189 if (cookiestr != NULL) {
190 log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server,
191 "Cookie2: [%s]", cookiestr);
192 /* Does the cookie string contain one with our name? */
193 isvalid = ap_strstr_c(cookiestr, a);
194 if (isvalid != NULL) {
195 /* Move past the cookie name and equal sign */
196 isvalid += strlen(a) + 1;
197 /* Duplicate it into the pool */
198 cookiebuf = apr_pstrdup(r->pool, isvalid);
199 /* Segregate just this cookie out of the string
200 * with a terminating nul at the first semicolon */
201 cookieend = ap_strchr(cookiebuf, ';');
202 if (cookieend != NULL)
203 *cookieend = '\0';
204 return cookiebuf;
205 }
206 }
207
208 cookiestr = (char *)apr_table_get(r->headers_in, "cookie");
209 if (cookiestr != NULL) {
210 log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server,
211 "Cookie: [%s]", cookiestr);
212 isvalid = ap_strstr_c(cookiestr, a);
213 if (isvalid != NULL) {
214 isvalid += strlen(a) + 1;
215 cookiebuf = apr_pstrdup(r->pool, isvalid);
216 cookieend = ap_strchr(cookiebuf, ';');
217 if (cookieend != NULL)
218 *cookieend = '\0';
219 return cookiebuf;
220 }
221 }
222
223 cookiestr = apr_table_get(r->headers_out, "set-cookie");
224 if (cookiestr != NULL) {
225 log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server,
226 "Set-Cookie: [%s]", cookiestr);
227 isvalid = ap_strstr_c(cookiestr, a);
228 if (isvalid != NULL) {
229 isvalid += strlen(a) + 1;
230 cookiebuf = apr_pstrdup(r->pool, isvalid);
231 cookieend = ap_strchr(cookiebuf, ';');
232 if (cookieend != NULL)
233 *cookieend = '\0';
234 return cookiebuf;
235 }
236 }
237 }
238
239 return "-";
240}
241
242/*static const char *extract_cookie(request_rec *r, char *a)
243{
244 logsql_state *cls = ap_get_module_config(r->server->module_config,
245 &log_sql_module);
246
247 return extract_specific_cookie(r, (char *)cls->cookie_name);
248}*/
249
250static const char *extract_unique_id(request_rec *r, char *a)
251{
252 const char *tempid;
253
254 tempid = apr_table_get(r->subprocess_env, "UNIQUE_ID");
255 if (!tempid)
256 return "-";
257 else
258 return tempid;
259}
260
261/* End declarations of various extract_ functions */
diff --git a/src/functions13.h b/src/functions13.h
new file mode 100644
index 0000000..54e3138
--- /dev/null
+++ b/src/functions13.h
@@ -0,0 +1,58 @@
1/* $Id$ */
2
3static const char *extract_bytes_sent(request_rec *r, char *a)
4{
5 if (!r->sent_bodyct) {
6 return "-";
7 }
8 else {
9 long int bs;
10 ap_bgetopt(r->connection->client, BO_BYTECT, &bs);
11 return ap_psprintf(r->pool, "%ld", bs);
12 }
13}
14
15static const char *extract_request_time(request_rec *r, char *a)
16{
17 int timz;
18 struct tm *t;
19 char tstr[MAX_STRING_LEN];
20
21 t = ap_get_gmtoff(&timz);
22
23 if (a && *a) { /* Custom format */
24 strftime(tstr, MAX_STRING_LEN, a, t);
25 } else { /* CLF format */
26 char sign = (timz < 0 ? '-' : '+');
27
28 if (timz < 0) {
29 timz = -timz;
30 }
31 strftime(tstr, MAX_STRING_LEN, "[%d/%b/%Y:%H:%M:%S ", t);
32 ap_snprintf(tstr + strlen(tstr), sizeof(tstr) - strlen(tstr), "%c%.2d%.2d]", sign, timz / 60, timz % 60);
33 }
34
35 return ap_pstrdup(r->pool, tstr);
36}
37
38static const char *extract_request_duration(request_rec *r, char *a)
39{
40 return ap_psprintf(r->pool, "%ld", time(NULL) - r->request_time);
41}
42
43static const char *extract_request_timestamp(request_rec *r, char *a)
44{
45 return ap_psprintf(r->pool, "%ld", (long) time(NULL));
46}
47static const char *extract_connection_status(request_rec *r, char *a) __attribute__((unused));
48static const char *extract_connection_status(request_rec *r, char *a)
49{
50 if (r->connection->aborted)
51 return "X";
52
53 if ((r->connection->keepalive) &&
54 ((r->server->keep_alive_max - r->connection->keepalives) > 0)) {
55 return "+";
56 }
57 return "-";
58}
diff --git a/src/functions20.h b/src/functions20.h
new file mode 100644
index 0000000..5220b7c
--- /dev/null
+++ b/src/functions20.h
@@ -0,0 +1,116 @@
1/* $Id$ */
2
3static const char *extract_bytes_sent(request_rec *r, char *a)
4{
5 if (!r->sent_bodyct || !r->bytes_sent) {
6 return "-";
7 } else {
8 return apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->bytes_sent);
9 }
10}
11
12static const char *extract_request_time_custom(request_rec *r, char *a,
13 apr_time_exp_t *xt)
14{
15 apr_size_t retcode;
16 char tstr[MAX_STRING_LEN];
17 apr_strftime(tstr, &retcode, sizeof(tstr), a, xt);
18 return apr_pstrdup(r->pool, tstr);
19}
20
21#define DEFAULT_REQUEST_TIME_SIZE 32
22typedef struct {
23 unsigned t;
24 char timestr[DEFAULT_REQUEST_TIME_SIZE];
25 unsigned t_validate;
26} cached_request_time;
27
28#define TIME_CACHE_SIZE 4
29#define TIME_CACHE_MASK 3
30static cached_request_time request_time_cache[TIME_CACHE_SIZE];
31
32static const char *extract_request_time(request_rec *r, char *a)
33{
34 apr_time_exp_t xt;
35
36 /* Please read comments in mod_log_config.h for more info about
37 * the I_INSIST....COMPLIANCE define
38 */
39 if (a && *a) { /* Custom format */
40#ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE
41 ap_explode_recent_localtime(&xt, apr_time_now());
42#else
43 ap_explode_recent_localtime(&xt, r->request_time);
44#endif
45 return extract_request_time_custom(r, a, &xt);
46 } else { /* CLF format */
47 /* This code uses the same technique as ap_explode_recent_localtime():
48 * optimistic caching with logic to detect and correct race conditions.
49 * See the comments in server/util_time.c for more information.
50 */
51 cached_request_time* cached_time = apr_palloc(r->pool,
52 sizeof(*cached_time));
53#ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE
54 apr_time_t request_time = apr_time_now();
55#else
56 apr_time_t request_time = r->request_time;
57#endif
58 unsigned t_seconds = (unsigned)apr_time_sec(request_time);
59 unsigned i = t_seconds & TIME_CACHE_MASK;
60 memcpy(cached_time, &(request_time_cache[i]), sizeof(*cached_time));
61 if ((t_seconds != cached_time->t) ||
62 (t_seconds != cached_time->t_validate)) {
63
64 /* Invalid or old snapshot, so compute the proper time string
65 * and store it in the cache
66 */
67 char sign;
68 int timz;
69
70 ap_explode_recent_localtime(&xt, r->request_time);
71 timz = xt.tm_gmtoff;
72 if (timz < 0) {
73 timz = -timz;
74 sign = '-';
75 }
76 else {
77 sign = '+';
78 }
79 cached_time->t = t_seconds;
80 apr_snprintf(cached_time->timestr, DEFAULT_REQUEST_TIME_SIZE,
81 "[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]",
82 xt.tm_mday, apr_month_snames[xt.tm_mon],
83 xt.tm_year+1900, xt.tm_hour, xt.tm_min, xt.tm_sec,
84 sign, timz / (60*60), timz % (60*60));
85 cached_time->t_validate = t_seconds;
86 memcpy(&(request_time_cache[i]), cached_time,
87 sizeof(*cached_time));
88 }
89 return cached_time->timestr;
90 }
91}
92
93static const char *extract_request_duration(request_rec *r, char *a)
94{
95 apr_time_t duration = apr_time_now() - r->request_time;
96 return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(duration));
97}
98
99static const char *extract_request_timestamp(request_rec *r, char *a)
100{
101 return apr_psprintf(r->pool, "%"APR_TIME_T_FMT, apr_time_sec(apr_time_now()));
102}
103
104static const char *extract_connection_status(request_rec *r, char *a) __attribute__((unused));
105static const char *extract_connection_status(request_rec *r, char *a)
106{
107 if (r->connection->aborted)
108 return "X";
109
110 if (r->connection->keepalive == AP_CONN_KEEPALIVE &&
111 (!r->server->keep_alive_max ||
112 (r->server->keep_alive_max - r->connection->keepalives) > 0)) {
113 return "+";
114 }
115 return "-";
116}
diff --git a/src/mod_log_sql.c b/src/mod_log_sql.c
new file mode 100644
index 0000000..be4e1f9
--- /dev/null
+++ b/src/mod_log_sql.c
@@ -0,0 +1,1578 @@
1/* $Id:mod_log_sql.c 180 2008-09-21 15:54:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#elif defined(WITH_APACHE13)
6# include "apache13.h"
7#else
8# error Unsupported Apache version
9#endif
10
11#ifdef HAVE_CONFIG_H
12/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
13 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
14 */
15#undef PACKAGE_BUGREPORT
16#undef PACKAGE_NAME
17#undef PACKAGE_STRING
18#undef PACKAGE_TARNAME
19#undef PACKAGE_VERSION
20
21#include "autoconfig.h"
22#endif
23
24#if APR_HAVE_UNISTD_H
25#include <unistd.h>
26#endif
27#ifdef HAVE_LIMITS_H
28#include <limits.h>
29#endif
30
31#include "mod_log_sql.h"
32
33/* Configuratino Defaults */
34#define DEFAULT_TRANSFER_LOG_FMT "AbHhmRSsTUuv"
35#define DEFAULT_NOTES_TABLE_NAME "notes"
36#define DEFAULT_HIN_TABLE_NAME "headers_in"
37#define DEFAULT_HOUT_TABLE_NAME "headers_out"
38#define DEFAULT_COOKIE_TABLE_NAME "cookies"
39#define DEFAULT_PRESERVE_FILE "logs/mod_log_sql-preserve"
40
41/* -------------*
42 * DECLARATIONS *
43 * -------------*/
44
45/* Declare ourselves so the configuration routines can find and know us. */
46module AP_MODULE_DECLARE_DATA log_sql_module;
47
48/* The contents of these are known 'Apache wide' and are not variable
49 * on a per-virtual-server basis. Every virtual server 'knows' the
50 * same versions of these variables.
51 */
52
53typedef struct {
54 int massvirtual;
55 int createtables;
56 int forcepreserve;
57 int disablepreserve;
58 char *machid;
59 int announce;
60 logsql_dbconnection db;
61 logsql_dbdriver *driver;
62 /** Show config support */
63 char *showconfig;
64 apr_file_t *showconfig_fp;
65} global_config_t;
66
67static global_config_t global_config;
68
69/* structure to hold helper function info */
70typedef struct {
71 const char *alias; /* The function alias */
72 logsql_item_func *func; /* The extraction function pointer */
73 int want_orig_req; /* if it requires the original request prior to internal redirection */
74} logsql_function;
75
76/* list of logsql_functions's for log types */
77static apr_array_header_t *logsql_function_list;
78
79/* structure to hold sqlfield mappings */
80typedef struct {
81 const char *alias; /* long name for item */
82 const char *funcalias; /* The function alias */
83 logsql_function *func; /* its extraction function */
84 char *param; /* Parameter for function */
85 const char *sql_field_name; /* its column in SQL */
86 char string_contents; /* Whether this is a string field or not */
87 logsql_field_datatype datatype; /* the field data type */
88 apr_size_t size; /* The size of the data type */
89} logsql_field;
90
91/* list of logsql_item's for log types */
92static apr_array_header_t *logsql_field_list;
93
94/* But the contents of this structure will vary by virtual server.
95 * This permits each virtual server to vary its configuration slightly
96 * for per-server customization.
97 *
98 * Each child process has its own segregated copy of this structure.
99 */
100typedef struct {
101 apr_array_header_t *transfer_ignore_list;
102 apr_array_header_t *transfer_accept_list;
103 apr_array_header_t *remhost_ignore_list;
104 apr_array_header_t *notes_list;
105 apr_array_header_t *hout_list;
106 apr_array_header_t *hin_list;
107 apr_array_header_t *cookie_list;
108 const char *notes_table_name;
109 const char *hout_table_name;
110 const char *hin_table_name;
111 const char *cookie_table_name;
112 const char *transfer_table_name;
113 apr_array_header_t *transfer_log_format;
114 apr_pool_t *parsed_pool;
115 logsql_field **parsed_log_format;
116 const char *preserve_file;
117 const char *cookie_name;
118} logsql_state;
119
120/** Registration function for extract functions
121 *
122 * This functions registers an alias for a function
123 *
124 * @note This is exported from the module
125 */
126LOGSQL_DECLARE(void) log_sql_register_function(apr_pool_t *p,
127 const char *alias, logsql_item_func *func,
128 logsql_function_req want_orig_req)
129{
130 logsql_function *item;
131 if (!logsql_function_list)
132 logsql_function_list = apr_array_make(p,10, sizeof(logsql_function));
133
134 item = apr_array_push(logsql_function_list);
135 item->alias = alias;
136 item->func = func;
137 item->want_orig_req = want_orig_req;
138 if (global_config.showconfig_fp) {
139 apr_file_printf(global_config.showconfig_fp," Function : %s\n",alias);
140 }
141}
142/** Register a old style sql mapping to the new style
143 *
144 * @note This is exported from the module
145 */
146LOGSQL_DECLARE(void) log_sql_register_alias(server_rec *s, apr_pool_t *p,
147 char key, const char *alias)
148{
149 server_rec *ts;
150 for (ts = s; ts; ts = ts->next) {
151 logsql_state *cfg = ap_get_module_config(ts->module_config,
152 &log_sql_module);
153 int itr;
154 for (itr = 0; itr < cfg->transfer_log_format->nelts; itr++) {
155 const char *logformat = ((const char **)cfg->transfer_log_format->elts)[itr];
156 //log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "Testing Logformat %s against %c for %s",logformat,key,alias);
157 // Check if it is only one character AND it is our key
158 if (logformat[1]=='\0' && logformat[0]==key) {
159 ((const char **)cfg->transfer_log_format->elts)[itr] = alias;
160 }
161 }
162 }
163}
164
165
166/** Registration sqlfield aliases to functions
167 *
168 * And update parse cache for transfer_log_format
169 *
170 * @note This is exported from the module
171 */
172LOGSQL_DECLARE(void) log_sql_register_field(apr_pool_t *p,
173 const char *alias,
174 const char *funcalias, const char *param,
175 const char *sql_field_name,
176 logsql_field_datatype datatype, apr_size_t size)
177{
178 logsql_field *item;
179
180 if (!logsql_field_list)
181 logsql_field_list = apr_array_make(p,10, sizeof(logsql_field));
182
183 item = apr_array_push(logsql_field_list);
184 item->func = NULL;
185 item->alias = apr_pstrdup(p, alias);
186 item->funcalias = apr_pstrdup(p, funcalias);
187 item->param = apr_pstrdup(p, param);
188 item->sql_field_name = apr_pstrdup(p,sql_field_name);
189 item->datatype = datatype;
190 item->string_contents = 0;
191 if (datatype == LOGSQL_DATATYPE_CHAR || datatype == LOGSQL_DATATYPE_VARCHAR) {
192 item->string_contents = 1;
193 }
194 item->size = size;
195}
196
197/**
198 * Links sql field items with their functions
199 */
200LOGSQL_DECLARE(void) log_sql_register_finish(server_rec *s)
201{
202 server_rec *ts;
203 int itr, f;
204 logsql_field *item;
205 logsql_function *func;
206 for (itr = 0; itr < logsql_field_list->nelts; itr++) {
207 item = &((logsql_field *)logsql_field_list->elts)[itr];
208 if (item->func) continue;
209 /* Find function alias in function list */
210 for (f = 0; f < logsql_function_list->nelts; f++) {
211 func = &((logsql_function *)logsql_function_list->elts)[f];
212 if (strcmp(func->alias,item->funcalias)==0) {
213 item->func = func;
214 if (global_config.showconfig_fp) {
215 apr_file_printf(global_config.showconfig_fp," Item : %s using function %s(%s)\n"
216 "\tStoring in field %s of type %s(%"APR_SIZE_T_FMT")\n",
217 item->alias, item->funcalias, item->param,
218 item->sql_field_name, item->string_contents ? "TEXT":"NUMERIC", item->size);
219 }
220 break;
221 }
222 }
223 if (!item->func) {
224 log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
225 "Could not find function %s for item %s",item->funcalias, item->alias);
226 }
227 }
228 /* some voodoo here to post parse logitems in all servers *
229 * so a "cached" list is used in the main logging loop for speed */
230 for (ts = s; ts; ts = ts->next) {
231 logsql_state *cfg = ap_get_module_config(ts->module_config,
232 &log_sql_module);
233
234 if (!cfg->parsed_log_format) {
235 cfg->parsed_log_format = apr_pcalloc(cfg->parsed_pool,
236 cfg->transfer_log_format->nelts * sizeof(logsql_field *));
237 }
238
239 for (itr = 0; itr < cfg->transfer_log_format->nelts; itr++) {
240 const char *logformat = ((char **)cfg->transfer_log_format->elts)[itr];
241 for (f = 0; f < logsql_field_list->nelts; f++) {
242 item = &((logsql_field *)logsql_field_list->elts)[f];
243 if (item->func && strcmp(logformat,item->alias)==0) {
244 cfg->parsed_log_format[itr] = item;
245 break;
246 }
247 }
248 }
249 }
250}
251
252/* Registration function for database drivers */
253LOGSQL_DECLARE(void) log_sql_register_driver(apr_pool_t *p,
254 logsql_dbdriver *driver)
255{
256 global_config.driver = driver;
257}
258
259/* Include all the core extract functions */
260#include "functions.h"
261#if defined(WITH_APACHE13)
262# include "functions13.h"
263#elif defined(WITH_APACHE20)
264# include "functions20.h"
265#endif
266
267static logsql_opendb_ret log_sql_opendb_link(server_rec* s)
268{
269 logsql_opendb_ret result;
270 if (global_config.driver == NULL) {
271 return LOGSQL_OPENDB_FAIL;
272 }
273 if (global_config.forcepreserve) {
274 /*global_config.db.connected = 1;*/
275 return LOGSQL_OPENDB_PRESERVE;
276 }
277 if (global_config.db.connected) {
278 return LOGSQL_OPENDB_ALREADY;
279 }
280 /* database
281 host
282 user
283 passwd
284 */
285 if (global_config.db.parms) {
286 result = global_config.driver->connect(s, &global_config.db);
287 if (result==LOGSQL_OPENDB_FAIL) {
288 global_config.db.connected = 0;
289 } else {
290 global_config.db.connected = 1;
291 }
292 return result;
293 } else {
294 log_error(APLOG_MARK, APLOG_ERR, 0, s,
295 "mod_log_sql: insufficient configuration info to establish database link");
296 return LOGSQL_OPENDB_FAIL;
297 }
298}
299
300static void preserve_entry(request_rec *r, const char *query)
301{
302 logsql_state *cls = ap_get_module_config(r->server->module_config,
303 &log_sql_module);
304 apr_status_t result;
305 apr_file_t *fp;
306
307 /* If preserve file is disabled bail out */
308 if (global_config.disablepreserve)
309 return;
310 #if defined(WITH_APACHE20)
311 result = apr_file_open(&fp, cls->preserve_file,APR_APPEND | APR_WRITE | APR_CREATE, APR_OS_DEFAULT, r->pool);
312 #elif defined(WITH_APACHE13)
313 fp = ap_pfopen(r->pool, cls->preserve_file, "a");
314 result = (fp)?0:errno;
315 #endif
316 if (result != APR_SUCCESS) {
317 log_error(APLOG_MARK, APLOG_ERR, result, r->server,
318 "attempted append of local preserve file '%s' but failed.",cls->preserve_file);
319 } else {
320 apr_file_printf(fp,"%s;\n", query);
321 #if defined(WITH_APACHE20)
322 apr_file_close(fp);
323 #elif defined(WITH_APACHE13)
324 ap_pfclose(r->pool, fp);
325 #endif
326 log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
327 "mod_log_sql: entry preserved in %s", cls->preserve_file);
328 }
329}
330
331
332/* ------------------------------------------------*
333 * Command handlers that are called according *
334 * to the directives found at Apache runtime. *
335 * ------------------------------------------------*/
336
337
338static const char *set_global_flag_slot(cmd_parms *cmd,
339 void *struct_ptr,
340 int flag)
341{
342 void *ptr = &global_config;
343 int offset = (int)(long)cmd->info;
344
345 *(int *)((char *)ptr + offset) = flag ? 1 : 0;
346
347 return NULL;
348}
349
350static const char *set_global_nmv_flag_slot(cmd_parms *cmd,
351 void *struct_ptr,
352 int flag)
353{
354 if (global_config.massvirtual) {
355 return apr_psprintf(cmd->pool,
356 "mod_log_sql: do not set %s when LogSQLMassVirtualHosting(%d) is On.%d:%d",
357 cmd->cmd->name, global_config.massvirtual,
358 (int)(long)&global_config, (int)(long)struct_ptr);
359 } else {
360 return set_global_flag_slot(cmd,struct_ptr,flag);
361 }
362}
363
364static const char *set_global_string_slot(cmd_parms *cmd,
365 void *struct_ptr,
366 const char *arg)
367{
368 void *ptr = &global_config;
369 int offset = (int)(long)cmd->info;
370
371 *(const char **)((char *)ptr + offset) = apr_pstrdup(cmd->pool,arg);
372 return NULL;
373}
374
375static const char *set_server_string_slot(cmd_parms *cmd,
376 void *struct_ptr,
377 const char *arg)
378{
379 void *ptr = ap_get_module_config(cmd->server->module_config,
380 &log_sql_module);
381 int offset = (int)(long)cmd->info;
382
383 *(const char **)((char *)ptr + offset) = arg;
384
385 return NULL;
386}
387
388static const char *set_server_file_slot(cmd_parms *cmd,
389 void *struct_ptr,
390 const char *arg)
391{
392 void *ptr = ap_get_module_config(cmd->server->module_config,
393 &log_sql_module);
394 int offset = (int)(long)cmd->info;
395 const char *path;
396
397 path = ap_server_root_relative(cmd->pool, (char *)arg);
398
399 if (!path) {
400 return apr_pstrcat(cmd->pool, "Invalid file path ",
401 arg, NULL);
402 }
403
404 *(const char **)((char*)ptr + offset) = path;
405
406 return NULL;
407}
408
409static apr_array_header_t *create_logformat_default(apr_pool_t *p)
410{
411 apr_array_header_t *logformat;
412 char **addme;
413
414 logformat = apr_array_make(p, 12, sizeof(char *));
415 addme = apr_array_push(logformat); *addme = "useragent";
416 addme = apr_array_push(logformat); *addme = "bytes_sent";
417 addme = apr_array_push(logformat); *addme = "request_protocol";
418 addme = apr_array_push(logformat); *addme = "remote_host";
419 addme = apr_array_push(logformat); *addme = "request_method";
420 addme = apr_array_push(logformat); *addme = "referer";
421 addme = apr_array_push(logformat); *addme = "timestamp";
422 addme = apr_array_push(logformat); *addme = "status";
423 addme = apr_array_push(logformat); *addme = "request_duration";
424 addme = apr_array_push(logformat); *addme = "request_uri";
425 addme = apr_array_push(logformat); *addme = "remote_user";
426 addme = apr_array_push(logformat); *addme = "virtual_host";
427 return logformat;
428}
429
430static const char *set_server_nmv_string_slot(cmd_parms *parms,
431 void *struct_ptr,
432 const char *arg)
433{
434 if (global_config.massvirtual)
435 return apr_psprintf(parms->pool,
436 "mod_log_sql: do not set %s when LogSQLMassVirtualHosting is On.",
437 parms->cmd->name);
438 else
439 return set_server_string_slot(parms,struct_ptr,arg);
440}
441
442/* Set a DB connection parameter */
443static const char *set_dbparam(cmd_parms *cmd,
444 void *struct_ptr,
445 const char *key,
446 const char *val)
447{
448 if (!global_config.db.parms) {
449 global_config.db.parms = apr_table_make(cmd->pool,5);
450 }
451 apr_table_set(global_config.db.parms,key,val);
452 return NULL;
453}
454
455static const char *set_dbparam_slot(cmd_parms *cmd,
456 void *struct_ptr,
457 const char *arg)
458{
459 const char *param = (char *)cmd->info;
460 set_dbparam(cmd,NULL,param,arg);
461 return NULL;
462}
463
464/* Sets basic connection info */
465static const char *set_log_sql_info(cmd_parms *cmd, void *dummy,
466 const char *host, const char *user, const char *pwd)
467{
468 if (!user) { /* user is null, so only one arg passed */
469 /* TODO: to more error checking/force all params to be set */
470 apr_uri_t uri;
471 apr_uri_parse(cmd->pool, host, &uri);
472 if (uri.scheme) {
473 set_dbparam(cmd, NULL, "driver", uri.scheme);
474 }
475 if (uri.hostname) {
476 set_dbparam(cmd, NULL, "hostname", uri.hostname);
477 }
478 if (uri.user) {
479 set_dbparam(cmd, NULL, "username", uri.user);
480 }
481 if (uri.password) {
482 set_dbparam(cmd, NULL, "password", uri.password);
483 }
484 if (uri.port_str) {
485 set_dbparam(cmd, NULL, "port", uri.port_str);
486 }
487 if (uri.path) {
488 /* extract Database name */
489 char *off = ap_strchr(++uri.path,'/');
490 if (off)
491 *off='\0';
492 set_dbparam(cmd, NULL, "database", uri.path);
493
494 }
495 } else {
496 if (*host != '.') {
497 set_dbparam(cmd, NULL, "hostname", host);
498 }
499 if (*user != '.') {
500 set_dbparam(cmd, NULL, "username", user);
501 }
502 if (*pwd != '.') {
503 set_dbparam(cmd, NULL, "password", pwd);
504 }
505 }
506 return NULL;
507}
508
509static const char *add_server_string_slot(cmd_parms *cmd,
510 void *struct_ptr,
511 const char *arg)
512{
513 char **addme;
514 void *ptr = ap_get_module_config(cmd->server->module_config,
515 &log_sql_module);
516 int offset = (int)(long)cmd->info;
517 apr_array_header_t *ary = *(apr_array_header_t **)((char *)ptr + offset);
518 addme = apr_array_push(ary);
519 *addme = apr_pstrdup(ary->pool, arg);
520
521 return NULL;
522}
523
524static const char *set_logformat_slot(cmd_parms *cmd,
525 void *struct_ptr,
526 const char *arg)
527{
528 const char *t;
529 char t2[2] = {'\0','\0'};
530 for (t = arg; *t != '\0'; t++) {
531 t2[0] = *t;
532 add_server_string_slot(cmd, NULL, t2);
533 }
534 return NULL;
535}
536
537static const char *set_register_field(cmd_parms *cmd,
538 void *struct_ptr,
539 const char *arg)
540{
541 char *alias, *funcalias, *param, *field, *datatype_s, *size_s;
542 logsql_field_datatype datatype;
543 apr_size_t size;
544
545 alias = ap_getword_white(cmd->pool, &arg);
546 funcalias = ap_getword_white(cmd->pool, &arg);
547 param = ap_getword_conf(cmd->pool, &arg);
548 field = ap_getword_white(cmd->pool, &arg);
549 datatype_s = ap_getword_white(cmd->pool, &arg);
550 size_s = ap_getword_white(cmd->pool, &arg);
551
552 if (strcasecmp("VARCHAR",datatype_s)==0) {
553 datatype = LOGSQL_DATATYPE_VARCHAR;
554 } else if (strcasecmp("INT",datatype_s)==0) {
555 datatype = LOGSQL_DATATYPE_INT;
556 } else if (strcasecmp("CHAR",datatype_s)==0) {
557 datatype = LOGSQL_DATATYPE_CHAR;
558 } else if (strcasecmp("SMALLINT",datatype_s)==0) {
559 datatype = LOGSQL_DATATYPE_SMALLINT;
560 } else if (strcasecmp("BIGINT",datatype_s)==0) {
561 datatype = LOGSQL_DATATYPE_BIGINT;
562 } else {
563 return apr_psprintf(cmd->pool, "Unknown data type %s",datatype_s);
564 }
565
566 size = atoi(size_s);
567
568 log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
569 "%s, %s, %s, %s, %s(%d), %s(%"APR_SIZE_T_FMT")",
570 alias, funcalias, param, field, datatype_s, datatype, size_s, size);
571
572 log_sql_register_field(cmd->pool, alias, funcalias, param,
573 field, datatype, size);
574
575 return NULL;
576}
577
578/*------------------------------------------------------------*
579 * Apache-specific hooks into the module code *
580 * that are defined in the array 'mysql_lgog_module' (at EOF) *
581 *------------------------------------------------------------*/
582/* Closing mysql link: child_exit(1.3), pool registration(2.0) */
583#if defined(WITH_APACHE20)
584static apr_status_t log_sql_close_link(void *data)
585{
586 if (global_config.driver)
587 global_config.driver->disconnect(&global_config.db);
588 return APR_SUCCESS;
589}
590#elif defined(WITH_APACHE13)
591static void log_sql_child_exit(server_rec *s, apr_pool_t *p)
592{
593 if (global_config.driver)
594 global_config.driver->disconnect(&global_config.db);
595}
596#endif
597
598/* Child Init */
599#if defined(WITH_APACHE20)
600static void log_sql_child_init(apr_pool_t *p, server_rec *s)
601#elif defined(WITH_APACHE13)
602static void log_sql_child_init(server_rec *s, apr_pool_t *p)
603#endif
604{
605 logsql_opendb_ret retval;
606# if defined(WITH_APACHE20)
607 /* Register cleanup hook to close DDB connection (apache 2 doesn't have child_exit) */
608 apr_pool_cleanup_register(p, NULL, log_sql_close_link, log_sql_close_link);
609# endif
610 /* Open a link to the database */
611 retval = log_sql_opendb_link(s);
612 switch (retval) {
613 case LOGSQL_OPENDB_FAIL:
614 if (global_config.driver==NULL) {
615 log_error(APLOG_MARK, APLOG_ERR, 0, s,
616 "mod_log_sql: Driver module not loaded");
617 } else {
618 log_error(APLOG_MARK, APLOG_ERR, 0, s,
619 "mod_log_sql: child spawned but unable to open database link");
620 }
621 break;
622 case LOGSQL_OPENDB_SUCCESS:
623 case LOGSQL_OPENDB_ALREADY:
624 log_error(APLOG_MARK,APLOG_DEBUG,0, s,
625 "mod_log_sql: open_logdb_link successful");
626 break;
627 case LOGSQL_OPENDB_PRESERVE:
628 log_error(APLOG_MARK,APLOG_DEBUG, 0, s,
629 "mod_log_sql: open_logdb_link said that preservation is forced");
630 break;
631 }
632}
633
634static apr_array_header_t *do_merge_array(apr_array_header_t *parent, apr_array_header_t *child, apr_pool_t *p);
635
636/* post_config / module_init */
637#if defined(WITH_APACHE20)
638static int log_sql_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
639#elif defined(WITH_APACHE13)
640static void log_sql_module_init(server_rec *s, apr_pool_t *p)
641#endif
642{
643 server_rec *cur_s;
644 const char *default_p = ap_server_root_relative(p, DEFAULT_PRESERVE_FILE);
645 apr_array_header_t *parent = NULL;
646
647 if (global_config.showconfig != NULL) {
648 const char *tempfile = ap_server_root_relative(p, global_config.showconfig);
649 apr_status_t result;
650 #if defined(WITH_APACHE20)
651 result = apr_file_open(&global_config.showconfig_fp, tempfile,APR_TRUNCATE | APR_WRITE | APR_CREATE, APR_OS_DEFAULT, p);
652 #elif defined(WITH_APACHE13)
653 global_config.showconfig_fp = ap_pfopen(p, tempfile, "w");
654 result = (fp)?0:errno;
655 #endif
656 if (result != APR_SUCCESS) {
657 log_error(APLOG_MARK, APLOG_ERR, result, s,
658 "attempted open of showconfig file '%s' failed.",tempfile);
659 global_config.showconfig_fp = NULL;
660 } else {
661 #if defined(WITH_APACHE20)
662 char temp_time[APR_RFC822_DATE_LEN];
663 apr_rfc822_date(temp_time,apr_time_now());
664 #elif defined(WITH_APACHE13)
665 char *temp_time = ap_get_time());
666 #endif
667 apr_file_printf(global_config.showconfig_fp,"Mod_log_sql Config dump created on %s\n", temp_time);
668 }
669 }
670
671 for (cur_s = s; cur_s != NULL; cur_s= cur_s->next) {
672 logsql_state *cls = ap_get_module_config(cur_s->module_config,
673 &log_sql_module);
674 /* ap_server_root_relative any default preserve file locations */
675 if (cls->preserve_file == DEFAULT_PRESERVE_FILE)
676 cls->preserve_file = default_p;
677
678 /* Post-process logformats */
679 if (!cur_s->is_virtual) {
680 parent = create_logformat_default(p);
681 cls->transfer_log_format = do_merge_array(parent, cls->transfer_log_format, p);
682 parent = cls->transfer_log_format;
683 } else {
684 cls->transfer_log_format = do_merge_array(parent, cls->transfer_log_format, p);
685 }
686 }
687
688 /* TODO: Add local_address, remote_address, connection_status */
689 /** Register functions */
690 /** register_function(p, funcname, func_ptr, which request_rec); */
691 log_sql_register_function(p, "useragent", extract_agent, LOGSQL_FUNCTION_REQ_ORIG);
692 log_sql_register_function(p, "request_args", extract_request_query, LOGSQL_FUNCTION_REQ_ORIG);
693 log_sql_register_function(p, "bytes_sent", extract_bytes_sent, LOGSQL_FUNCTION_REQ_FINAL);
694 log_sql_register_function(p, "cookie", extract_specific_cookie,LOGSQL_FUNCTION_REQ_FINAL);
695 log_sql_register_function(p, "request_file", extract_request_file, LOGSQL_FUNCTION_REQ_FINAL);
696 log_sql_register_function(p, "request_protocol",extract_request_protocol,LOGSQL_FUNCTION_REQ_FINAL);
697 log_sql_register_function(p, "remote_host", extract_remote_host, LOGSQL_FUNCTION_REQ_FINAL);
698 log_sql_register_function(p, "unique_id", extract_unique_id, LOGSQL_FUNCTION_REQ_FINAL);
699 log_sql_register_function(p, "remote_logname", extract_remote_logname, LOGSQL_FUNCTION_REQ_FINAL);
700 log_sql_register_function(p, "request_method", extract_request_method, LOGSQL_FUNCTION_REQ_FINAL);
701 log_sql_register_function(p, "machine_id", extract_machine_id, LOGSQL_FUNCTION_REQ_FINAL);
702 log_sql_register_function(p, "child_pid", extract_child_pid, LOGSQL_FUNCTION_REQ_FINAL);
703 log_sql_register_function(p, "server_port", extract_server_port, LOGSQL_FUNCTION_REQ_FINAL);
704 log_sql_register_function(p, "referrer", extract_referer, LOGSQL_FUNCTION_REQ_ORIG);
705 log_sql_register_function(p, "request_line", extract_request_line, LOGSQL_FUNCTION_REQ_ORIG);
706 log_sql_register_function(p, "timestamp", extract_request_timestamp,LOGSQL_FUNCTION_REQ_FINAL);
707 log_sql_register_function(p, "status", extract_status, LOGSQL_FUNCTION_REQ_ORIG);
708 log_sql_register_function(p, "request_duration",extract_request_duration,LOGSQL_FUNCTION_REQ_ORIG);
709 log_sql_register_function(p, "request_time", extract_request_time, LOGSQL_FUNCTION_REQ_FINAL);
710 log_sql_register_function(p, "remote_user", extract_remote_user, LOGSQL_FUNCTION_REQ_FINAL);
711 log_sql_register_function(p, "request_uri", extract_request_uri, LOGSQL_FUNCTION_REQ_ORIG);
712 log_sql_register_function(p, "virtual_host", extract_virtual_host, LOGSQL_FUNCTION_REQ_FINAL);
713 log_sql_register_function(p, "server_name", extract_server_name, LOGSQL_FUNCTION_REQ_FINAL);
714
715 /** Old style aliases */
716 /** register_alias(s, shortname, longname) */
717 log_sql_register_alias(s,p,'A',"useragent");
718 log_sql_register_alias(s,p,'a',"request_args");
719 log_sql_register_alias(s,p,'b',"bytes_sent");
720 log_sql_register_alias(s,p,'c',"cookie");
721 log_sql_register_alias(s,p,'f',"request_file");
722 log_sql_register_alias(s,p,'H',"request_protocol");
723 log_sql_register_alias(s,p,'h',"remote_host");
724 log_sql_register_alias(s,p,'I',"unique_id");
725 log_sql_register_alias(s,p,'l',"remote_logname");
726 log_sql_register_alias(s,p,'m',"request_method");
727 log_sql_register_alias(s,p,'M',"machine_id");
728 log_sql_register_alias(s,p,'P',"child_pid");
729 log_sql_register_alias(s,p,'p',"server_port");
730 log_sql_register_alias(s,p,'R',"referrer");
731 log_sql_register_alias(s,p,'r',"request_line");
732 log_sql_register_alias(s,p,'S',"timestamp");
733 log_sql_register_alias(s,p,'s',"status");
734 log_sql_register_alias(s,p,'T',"request_duration");
735 log_sql_register_alias(s,p,'t',"request_time");
736 log_sql_register_alias(s,p,'u',"remote_user");
737 log_sql_register_alias(s,p,'U',"request_uri");
738 log_sql_register_alias(s,p,'v',"virtual_host");
739 log_sql_register_alias(s,p,'V',"server_name");
740
741 /* Register handlers */
742 /** register_field(s,p, longname, funcalias, arg,
743 * sqlfieldname, DATATYPE, DATA LENGTH); */
744 log_sql_register_field(p,"useragent", "useragent",NULL,
745 "agent", LOGSQL_DATATYPE_VARCHAR, 0);
746 log_sql_register_field(p,"request_args", "request_args",NULL,
747 "request_args",LOGSQL_DATATYPE_VARCHAR,0);
748 log_sql_register_field(p,"bytes_sent", "bytes_sent",NULL,
749 "bytes_sent",LOGSQL_DATATYPE_INT,0);
750 log_sql_register_field(p,"cookie", "cookie","Apache",
751 "cookie",LOGSQL_DATATYPE_VARCHAR,0);
752 log_sql_register_field(p,"request_file", "request_file",NULL,
753 "request_file",LOGSQL_DATATYPE_VARCHAR,0);
754 log_sql_register_field(p,"request_protocol", "request_protocol",NULL,
755 "request_protocol",LOGSQL_DATATYPE_VARCHAR,0);
756 log_sql_register_field(p,"remote_host", "remote_host",NULL,
757 "remote_host",LOGSQL_DATATYPE_VARCHAR,0);
758 log_sql_register_field(p,"unique_id", "unique_id",NULL,
759 "id",LOGSQL_DATATYPE_VARCHAR,0);
760 log_sql_register_field(p,"remote_logname", "remote_logname",NULL,
761 "remote_logname",LOGSQL_DATATYPE_VARCHAR,0);
762 log_sql_register_field(p,"request_method", "request_method",NULL,
763 "request_method",LOGSQL_DATATYPE_VARCHAR,0);
764 log_sql_register_field(p,"machine_id", "machine_id",NULL,
765 "machine_id",LOGSQL_DATATYPE_VARCHAR,0);
766 log_sql_register_field(p,"child_pid", "child_pid",NULL,
767 "child_pid",LOGSQL_DATATYPE_INT,0);
768 log_sql_register_field(p,"server_port", "server_port",NULL,
769 "server_port",LOGSQL_DATATYPE_INT,0);
770 log_sql_register_field(p,"referer", "referrer",NULL,
771 "referer",LOGSQL_DATATYPE_VARCHAR,0);
772 log_sql_register_field(p,"referrer", "referrer",NULL,
773 "referer",LOGSQL_DATATYPE_VARCHAR,0);
774 log_sql_register_field(p,"request_line", "request_line",NULL,
775 "request_line",LOGSQL_DATATYPE_VARCHAR,0);
776 log_sql_register_field(p,"timestamp", "timestamp",NULL,
777 "time_stamp",LOGSQL_DATATYPE_INT,0);
778 log_sql_register_field(p,"status", "status",NULL,
779 "status",LOGSQL_DATATYPE_INT,0);
780 log_sql_register_field(p,"request_duration", "request_duration",NULL,
781 "request_duration",LOGSQL_DATATYPE_INT,0);
782 log_sql_register_field(p,"request_time", "request_time",NULL,
783 "request_time",LOGSQL_DATATYPE_VARCHAR,0);
784 log_sql_register_field(p,"remote_user", "remote_user",NULL,
785 "remote_user",LOGSQL_DATATYPE_VARCHAR,0);
786 log_sql_register_field(p,"request_uri", "request_uri",NULL,
787 "request_uri",LOGSQL_DATATYPE_VARCHAR,0);
788 log_sql_register_field(p,"virtual_host", "virtual_host",NULL,
789 "virtual_host",LOGSQL_DATATYPE_VARCHAR,0);
790 log_sql_register_field(p,"server_name", "server_name",NULL,
791 "virtual_host",LOGSQL_DATATYPE_VARCHAR,0);
792
793 log_sql_register_finish(s);
794
795 if (global_config.announce) {
796 ap_add_version_component(p, PACKAGE_NAME"/"PACKAGE_VERSION);
797 }
798 global_config.db.p = p;
799
800#if defined(WITH_APACHE20)
801 return OK;
802#endif
803}
804
805/* This function handles calling the DB module, handling errors
806 * of missing tables and lost DB connections, and falling back to
807 * preserving the DB query.
808 *
809 * Parms: request record, table type, table name, and the full SQL command
810 */
811
812static logsql_query_ret safe_sql_insert(request_rec *r, logsql_tabletype table_type,
813 const char *table_name, const char *query) {
814
815 logsql_query_ret result;
816 logsql_state *cls = ap_get_module_config(r->server->module_config,
817 &log_sql_module);
818
819 if (!global_config.db.connected || global_config.driver == NULL) {
820 /* preserve query */
821 return LOGSQL_QUERY_NOLINK;
822 }
823
824 result = global_config.driver->insert(r,&global_config.db,query);
825
826 /* If we ran the query and it returned an error, try to be robust.
827 * (After all, the module thought it had a valid mysql_log connection but the query
828 * could have failed for a number of reasons, so we have to be extra-safe and check.) */
829 switch (result) {
830 case LOGSQL_QUERY_SUCCESS:
831 return LOGSQL_QUERY_SUCCESS;
832 case LOGSQL_QUERY_NOLINK:
833 return LOGSQL_QUERY_FAIL;
834 /* TODO: What do we do here */
835 case LOGSQL_QUERY_FAIL:
836 global_config.driver->disconnect(&global_config.db);
837 global_config.db.connected = 0;
838 /* re-open the connection and try again */
839 if (log_sql_opendb_link(r->server) != LOGSQL_OPENDB_FAIL) {
840 log_error(APLOG_MARK,APLOG_NOTICE,0, r->server,"db reconnect successful");
841# if defined(WITH_APACHE20)
842 apr_sleep(apr_time_from_sec(0.25)); /* pause for a quarter second */
843# elif defined(WITH_APACHE13)
844# if defined(WIN32)
845 Sleep((DWORD)0.25);
846# else
847 {
848 struct timespec delay, remainder;
849 int nanoret;
850 delay.tv_sec = 0;
851 delay.tv_nsec = 250000000; /* pause for a quarter second */
852 nanoret = nanosleep(&delay, &remainder);
853 if (nanoret && errno != EINTR) {
854 log_error(APLOG_MARK,APLOG_ERR, errno, r->server,"nanosleep unsuccessful");
855 }
856 }
857# endif /* win32 */
858# endif
859 result = global_config.driver->insert(r,&global_config.db,query);
860 if (result == LOGSQL_QUERY_SUCCESS) {
861 return LOGSQL_QUERY_SUCCESS;
862 } else {
863 log_error(APLOG_MARK,APLOG_ERR,0,r->server,"second attempt failed");
864 preserve_entry(r, query);
865 return LOGSQL_QUERY_PRESERVED;
866 }
867 } else {
868 log_error(APLOG_MARK,APLOG_ERR,0,r->server,
869 "reconnect failed, unable to reach database. SQL logging stopped until child regains a db connection.");
870 log_error(APLOG_MARK,APLOG_ERR,0,r->server,
871 "log entries are being preserved in %s",cls->preserve_file);
872 preserve_entry(r, query);
873 return LOGSQL_QUERY_PRESERVED;
874 }
875 break;
876 case LOGSQL_QUERY_NOTABLE:
877 if (global_config.createtables) {
878 log_error(APLOG_MARK,APLOG_ERR,0,r->server,
879 "table doesn't exist...creating now");
880 if ((result = global_config.driver->create_table(r, &global_config.db, table_type,
881 table_name))!=LOGSQL_TABLE_SUCCESS) {
882 log_error(APLOG_MARK,APLOG_ERR,result,r->server,
883 "child attempted but failed to create one or more tables for %s, preserving query", ap_get_server_name(r));
884 preserve_entry(r, query);
885 return LOGSQL_QUERY_PRESERVED;
886 } else {
887 log_error(APLOG_MARK,APLOG_ERR,result, r->server,
888 "tables successfully created - retrying query");
889 if ((result = global_config.driver->insert(r,&global_config.db,query))!=LOGSQL_QUERY_SUCCESS) {
890 log_error(APLOG_MARK,APLOG_ERR,result, r->server,
891 "giving up, preserving query");
892 preserve_entry(r, query);
893 return LOGSQL_QUERY_PRESERVED;
894 } else {
895 log_error(APLOG_MARK,APLOG_NOTICE,0, r->server,
896 "query successful after table creation");
897 return LOGSQL_QUERY_SUCCESS;
898 }
899 }
900 } else {
901 log_error(APLOG_MARK,APLOG_ERR,0,r->server,
902 "table doesn't exist, creation denied by configuration, preserving query");
903 preserve_entry(r, query);
904 return LOGSQL_QUERY_PRESERVED;
905 }
906 break;
907 default:
908 log_error(APLOG_MARK,APLOG_ERR,0, r->server,
909 "Invalid return code from mog_log_query");
910 return LOGSQL_QUERY_FAIL;
911 break;
912 }
913 return LOGSQL_QUERY_FAIL;
914}
915
916/* This function gets called to create a per-server configuration
917 * record. It will always be called for the main server and
918 * for each virtual server that is established. Each server maintains
919 * its own state that is separate from the others' states.
920 *
921 * The return value is a pointer to the created module-specific
922 * structure.
923 */
924static void *log_sql_make_state(apr_pool_t *p, server_rec *s)
925{
926 logsql_state *cls = (logsql_state *) apr_pcalloc(p, sizeof(logsql_state));
927
928 /* These defaults are overridable in the httpd.conf file. */
929 cls->transfer_log_format = apr_array_make(p, 1, sizeof(char *));
930 cls->parsed_pool = p;
931
932 cls->notes_table_name = DEFAULT_NOTES_TABLE_NAME;
933 cls->hin_table_name = DEFAULT_HIN_TABLE_NAME;
934 cls->hout_table_name = DEFAULT_HOUT_TABLE_NAME;
935 cls->cookie_table_name = DEFAULT_COOKIE_TABLE_NAME;
936 cls->preserve_file = DEFAULT_PRESERVE_FILE;
937
938 cls->transfer_ignore_list = apr_array_make(p, 1, sizeof(char *));
939 cls->transfer_accept_list = apr_array_make(p, 1, sizeof(char *));
940 cls->remhost_ignore_list = apr_array_make(p, 1, sizeof(char *));
941 cls->notes_list = apr_array_make(p, 1, sizeof(char *));
942 cls->hin_list = apr_array_make(p, 1, sizeof(char *));
943 cls->hout_list = apr_array_make(p, 1, sizeof(char *));
944 cls->cookie_list = apr_array_make(p, 1, sizeof(char *));
945 return (void *) cls;
946}
947
948
949/* Iterates through an array of char* and searches for a matching element
950 * Returns 0 if not found, 1 if found */
951static int in_array(apr_array_header_t *ary, const char *elem)
952{
953 int itr;
954 for (itr = 0; itr < ary->nelts; itr++) {
955 if (!strcmp(elem,((char **)ary->elts)[itr])) {
956 return 1;
957 }
958 }
959 return 0;
960}
961
962
963/* Parse through lists and merge based on +/- prefixes */
964static apr_array_header_t *do_merge_array(apr_array_header_t *parent, apr_array_header_t *child, apr_pool_t *p)
965{
966 apr_array_header_t *ret;
967 ret = apr_array_make(p, 1, sizeof(char *));
968 if (apr_is_empty_array(child)) {
969 apr_array_cat(ret, parent);
970 } else {
971 apr_array_header_t *addlist, *dellist;
972 apr_pool_t *subp;
973 char **elem, **ptr = (char **)(child->elts);
974 int itr, overwrite = 0;
975
976 apr_pool_create(&subp,p);
977
978 addlist = apr_array_make(subp,5,sizeof(char *));
979 dellist = apr_array_make(subp,5,sizeof(char *));
980
981 for (itr=0; itr<child->nelts; itr++) {
982 if (*ptr[itr] == '+') {
983 elem = (char **)apr_array_push(addlist);
984 *elem = (ptr[itr]+1);
985 } else if (*ptr[itr] == '-') {
986 elem = (char **)apr_array_push(dellist);
987 *elem = (ptr[itr]+1);
988 } else {
989 overwrite = 1;
990 elem = (char **)apr_array_push(addlist);
991 *elem = ptr[itr];
992 }
993 }
994 child = apr_array_make(p,1,sizeof(char *));
995 ptr = (char **)(parent->elts);
996 if (overwrite==0) {
997 /* if we are not overwriting the existing then prepare for merge */
998 for (itr=0; itr<parent->nelts; itr++) {
999 if (!in_array(addlist, ptr[itr]) && !in_array(dellist,ptr[itr])) {
1000 elem = apr_array_push(ret);
1001 *elem = apr_pstrdup(p, ptr[itr]);
1002 }
1003 }
1004 }
1005 apr_array_cat(ret, addlist);
1006 apr_pool_destroy(subp);
1007 }
1008 return ret;
1009}
1010
1011static void *log_sql_merge_state(apr_pool_t *p, void *basev, void *addv)
1012{
1013 /* Fetch the two states to merge */
1014 logsql_state *parent = (logsql_state *) basev;
1015 logsql_state *child = (logsql_state *) addv;
1016
1017 /* Child can override these, otherwise they default to parent's choice.
1018 * If the parent didn't set them, create reasonable defaults for the
1019 * ones that should have such default settings. Leave the others null. */
1020
1021 /* No default for transfer_table_name because we want its absence
1022 * to disable logging. */
1023 if (!child->transfer_table_name) {
1024 child->transfer_table_name = parent->transfer_table_name;
1025 }
1026
1027 if (child->preserve_file == DEFAULT_PRESERVE_FILE)
1028 child->preserve_file = parent->preserve_file;
1029 /* server_root_relative the preserve file location */
1030 if (child->preserve_file == DEFAULT_PRESERVE_FILE)
1031 child->preserve_file = ap_server_root_relative(p, DEFAULT_PRESERVE_FILE);
1032
1033 if (child->notes_table_name == DEFAULT_NOTES_TABLE_NAME)
1034 child->notes_table_name = parent->notes_table_name;
1035
1036 if (child->hin_table_name == DEFAULT_HIN_TABLE_NAME)
1037 child->hin_table_name = parent->hin_table_name;
1038
1039 if (child->hout_table_name == DEFAULT_HOUT_TABLE_NAME)
1040 child->hout_table_name = parent->hout_table_name;
1041
1042 if (child->cookie_table_name == DEFAULT_COOKIE_TABLE_NAME)
1043 child->cookie_table_name = parent->cookie_table_name;
1044
1045 child->transfer_ignore_list = do_merge_array(parent->transfer_ignore_list, child->transfer_ignore_list, p);
1046 child->transfer_accept_list = do_merge_array(parent->transfer_accept_list, child->transfer_accept_list, p);
1047 child->remhost_ignore_list = do_merge_array(parent->remhost_ignore_list, child->remhost_ignore_list, p);
1048 child->notes_list = do_merge_array(parent->notes_list, child->notes_list, p);
1049 child->hin_list = do_merge_array(parent->hin_list, child->hin_list, p);
1050 child->hout_list = do_merge_array(parent->hout_list, child->hout_list, p);
1051 child->cookie_list = do_merge_array(parent->cookie_list,child->cookie_list, p);
1052
1053 if (!child->cookie_name)
1054 child->cookie_name = parent->cookie_name;
1055
1056
1057 return (void*) child;
1058}
1059
1060/* Routine to perform the actual construction and execution of the relevant
1061 * INSERT statements.
1062 */
1063static int log_sql_transaction(request_rec *orig)
1064{
1065 char **ptrptr, **ptrptr2;
1066 logsql_state *cls = ap_get_module_config(orig->server->module_config, &log_sql_module);
1067 const char *access_query;
1068 request_rec *r;
1069 const char *transfer_tablename = cls->transfer_table_name;
1070 const char *notes_tablename = cls->notes_table_name;
1071 const char *hout_tablename = cls->hout_table_name;
1072 const char *hin_tablename = cls->hin_table_name;
1073 const char *cookie_tablename = cls->cookie_table_name;
1074 if (global_config.driver == NULL) {
1075 return OK;
1076 }
1077 /* We handle mass virtual hosting differently. Dynamically determine the name
1078 * of the table from the virtual server's name, and flag it for creation.
1079 */
1080 if (global_config.massvirtual) {
1081 /* TODO: Make these configurable? */
1082 char *access_base = "access_";
1083 char *notes_base = "notes_";
1084 char *hout_base = "headout_";
1085 char *hin_base = "headin_";
1086 char *cookie_base = "cookies_";
1087
1088
1089 /* Determine the hostname and convert it to all lower-case; */
1090 char *servername = apr_pstrdup(orig->pool,(char *)ap_get_server_name(orig));
1091
1092 char *p=servername;
1093 while (*p) {
1094 *p = apr_tolower(*p);
1095 if (*p == '.') *p = '_';
1096 if (*p == '-') *p = '_';
1097 ++p;
1098 }
1099
1100 /* Find memory long enough to hold the table name + \0. */
1101 transfer_tablename = apr_pstrcat(orig->pool, access_base, servername, NULL);
1102 notes_tablename = apr_pstrcat(orig->pool, notes_base, servername, NULL);
1103 hin_tablename = apr_pstrcat(orig->pool, hin_base, servername, NULL);
1104 hout_tablename = apr_pstrcat(orig->pool, hout_base, servername, NULL);
1105 cookie_tablename = apr_pstrcat(orig->pool, cookie_base, servername, NULL);
1106
1107 /* Tell this virtual server its transfer table name, and
1108 * turn on create_tables, which is implied by massvirtual.
1109 */
1110
1111 global_config.createtables = 1;
1112 }
1113
1114 /* Do we have enough info to log? */
1115 if (!transfer_tablename) {
1116 return DECLINED;
1117 } else {
1118 const char *thehost;
1119 const char *theitem;
1120 char *fields = "", *values = "";
1121 char *itemsets = "";
1122 char *note_query = NULL;
1123 char *hin_query = NULL;
1124 char *hout_query = NULL;
1125 char *cookie_query = NULL;
1126 const char *unique_id;
1127 const char *formatted_item;
1128 int i, showcomma;
1129 int proceed;
1130
1131 for (r = orig; r->next; r = r->next) {
1132 continue;
1133 }
1134
1135 /* The following is a stolen upsetting mess of pointers, I'm sorry.
1136 * Anyone with the motiviation and/or the time should feel free
1137 * to make this cleaner. :) */
1138 ptrptr2 = (char **) (cls->transfer_accept_list->elts + (cls->transfer_accept_list->nelts * cls->transfer_accept_list->elt_size));
1139
1140 /* Go through each element of the accept list and compare it to the
1141 * request_uri. If we don't get a match, return without logging */
1142 if ((r->uri) && (cls->transfer_accept_list->nelts)) {
1143 proceed = 0;
1144 for (ptrptr = (char **) cls->transfer_accept_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->transfer_accept_list->elt_size))
1145 if (ap_strstr(r->uri, *ptrptr)) {
1146 proceed = 1;
1147 break;
1148 }
1149 if (!proceed)
1150 return OK;
1151 }
1152
1153 /* Go through each element of the ignore list and compare it to the
1154 * request_uri. If we get a match, return without logging */
1155 ptrptr2 = (char **) (cls->transfer_ignore_list->elts + (cls->transfer_ignore_list->nelts * cls->transfer_ignore_list->elt_size));
1156 if (r->uri) {
1157 for (ptrptr = (char **) cls->transfer_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->transfer_ignore_list->elt_size))
1158 if (ap_strstr(r->uri, *ptrptr)) {
1159 return OK;
1160 }
1161 }
1162
1163 /* Go through each element of the ignore list and compare it to the
1164 * remote host. If we get a match, return without logging */
1165 ptrptr2 = (char **) (cls->remhost_ignore_list->elts + (cls->remhost_ignore_list->nelts * cls->remhost_ignore_list->elt_size));
1166 thehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL);
1167 if (thehost) {
1168 for (ptrptr = (char **) cls->remhost_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->remhost_ignore_list->elt_size))
1169 if (ap_strstr_c(thehost, *ptrptr)) {
1170 return OK;
1171 }
1172 }
1173
1174
1175 /* Iterate through the format characters and set up the INSERT string according to
1176 * what the user has configured. */
1177 showcomma = 0;
1178 for (i = 0; i<cls->transfer_log_format->nelts; i++) {
1179 logsql_field *item = cls->parsed_log_format[i];
1180 if (item==NULL || item->func==NULL) {
1181 log_error(APLOG_MARK, APLOG_ERR, 0, orig->server,
1182 "Log Format '%s' unknown or incomplete",((char **)cls->transfer_log_format->elts)[i]);
1183 continue;
1184 }
1185
1186 /* Yes, this key is one of the configured keys.
1187 * Call the key's function and put the returned value into 'formatted_item' */
1188 formatted_item = item->func->func(item->func->want_orig_req ? orig : r,
1189 item->param ? item->param : "");
1190
1191 /* Massage 'formatted_item' for proper SQL eligibility... */
1192 if (!formatted_item) {
1193 formatted_item = "";
1194 } else if (formatted_item[0] == '-' && formatted_item[1] == '\0' && !item->string_contents) {
1195 /* If apache tried to log a '-' character for a numeric field, convert that to a zero
1196 * because the database expects a numeral and will reject the '-' character. */
1197 formatted_item = "0";
1198 }
1199
1200 /* Append the fieldname and value-to-insert to the appropriate strings, quoting stringvals with ' as appropriate */
1201 fields = apr_pstrcat(r->pool, fields, (showcomma ? "," : ""),
1202 item->sql_field_name, NULL);
1203 values = apr_pstrcat(r->pool, values, (showcomma ? "," : ""),
1204 global_config.driver->escape(r, formatted_item, r->pool,&global_config.db), NULL);
1205 showcomma = 1;
1206 }
1207
1208 /* Work through the list of notes defined by LogSQLWhichNotes */
1209 i = 0;
1210 unique_id = extract_unique_id(r, "");
1211
1212 ptrptr2 = (char **) (cls->notes_list->elts + (cls->notes_list->nelts * cls->notes_list->elt_size));
1213 for (ptrptr = (char **) cls->notes_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->notes_list->elt_size)) {
1214 /* If the specified note (*ptrptr) exists for the current request... */
1215 if ((theitem = apr_table_get(r->notes, *ptrptr))) {
1216 itemsets = apr_pstrcat(r->pool, itemsets,
1217 (i > 0 ? "," : ""),
1218 "(",
1219 global_config.driver->escape(r, unique_id, r->pool, &global_config.db),
1220 ",",
1221 global_config.driver->escape(r, *ptrptr, r->pool,&global_config.db),
1222 ",",
1223 global_config.driver->escape(r, theitem, r->pool,&global_config.db),
1224 ")",
1225 NULL);
1226 i++;
1227 }
1228 }
1229 if ( *itemsets != '\0' ) {
1230 note_query = apr_psprintf(r->pool, "insert %s into %s (id, item, val) values %s",
1231 /*global_config.insertdelayed?"delayed":*/"", notes_tablename, itemsets);
1232
1233 log_error(APLOG_MARK,APLOG_DEBUG,0, orig->server,"mod_log_sql: note string: %s", note_query);
1234 }
1235
1236 /* Work through the list of headers-out defined by LogSQLWhichHeadersOut*/
1237 i = 0;
1238 itemsets = "";
1239
1240 ptrptr2 = (char **) (cls->hout_list->elts + (cls->hout_list->nelts * cls->hout_list->elt_size));
1241 for (ptrptr = (char **) cls->hout_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->hout_list->elt_size)) {
1242 /* If the specified header (*ptrptr) exists for the current request... */
1243 if ((theitem = apr_table_get(r->headers_out, *ptrptr))) {
1244 itemsets = apr_pstrcat(r->pool, itemsets,
1245 (i > 0 ? "," : ""),
1246 "(",
1247 global_config.driver->escape(r,unique_id, r->pool, &global_config.db),
1248 ",",
1249 global_config.driver->escape(r,*ptrptr, r->pool,&global_config.db),
1250 ",",
1251 global_config.driver->escape(r,theitem, r->pool,&global_config.db),
1252 ")",
1253 NULL);
1254 i++;
1255 }
1256 }
1257 if ( *itemsets != '\0' ) {
1258 hout_query = apr_psprintf(r->pool, "insert %s into %s (id, item, val) values %s",
1259 /*global_config.insertdelayed?"delayed":*/"", hout_tablename, itemsets);
1260
1261 log_error(APLOG_MARK,APLOG_DEBUG,0, orig->server,"mod_log_sql: header_out string: %s", hout_query);
1262 }
1263
1264
1265 /* Work through the list of headers-in defined by LogSQLWhichHeadersIn */
1266 i = 0;
1267 itemsets = "";
1268
1269 ptrptr2 = (char **) (cls->hin_list->elts + (cls->hin_list->nelts * cls->hin_list->elt_size));
1270 for (ptrptr = (char **) cls->hin_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->hin_list->elt_size)) {
1271 /* If the specified header (*ptrptr) exists for the current request... */
1272 if ((theitem = apr_table_get(r->headers_in, *ptrptr))) {
1273 itemsets = apr_pstrcat(r->pool, itemsets,
1274 (i > 0 ? "," : ""),
1275 "(",
1276 global_config.driver->escape(r,unique_id, r->pool, &global_config.db),
1277 ",",
1278 global_config.driver->escape(r,*ptrptr, r->pool,&global_config.db),
1279 ",",
1280 global_config.driver->escape(r,theitem, r->pool,&global_config.db),
1281 ")",
1282 NULL);
1283 i++;
1284 }
1285 }
1286 if ( *itemsets != '\0' ) {
1287 hin_query = apr_psprintf(r->pool, "insert %s into %s (id, item, val) values %s",
1288 /*global_config.insertdelayed?"delayed":*/"", hin_tablename, itemsets);
1289
1290 log_error(APLOG_MARK,APLOG_DEBUG,0, orig->server,"mod_log_sql: header_in string: %s", hin_query);
1291 }
1292
1293
1294 /* Work through the list of cookies defined by LogSQLWhichCookies */
1295 i = 0;
1296 itemsets = "";
1297
1298 ptrptr2 = (char **) (cls->cookie_list->elts + (cls->cookie_list->nelts * cls->cookie_list->elt_size));
1299 for (ptrptr = (char **) cls->cookie_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->cookie_list->elt_size)) {
1300 /* If the specified cookie (*ptrptr) exists for the current request... */
1301 if ( strncmp((theitem = extract_specific_cookie(r, *ptrptr)), "-", 1) ) {
1302 itemsets = apr_pstrcat(r->pool, itemsets,
1303 (i > 0 ? "," : ""),
1304 "(",
1305 global_config.driver->escape(r,unique_id, r->pool, &global_config.db),
1306 ",",
1307 global_config.driver->escape(r,*ptrptr, r->pool,&global_config.db),
1308 ",",
1309 global_config.driver->escape(r,theitem, r->pool,&global_config.db),
1310 ")",
1311 NULL);
1312 i++;
1313 }
1314
1315 }
1316 if ( *itemsets != '\0' ) {
1317 cookie_query = apr_psprintf(r->pool, "insert %s into %s (id, item, val) values %s",
1318 /*global_config.insertdelayed?"delayed":*/"", cookie_tablename, itemsets);
1319
1320 log_error(APLOG_MARK,APLOG_DEBUG,0, orig->server,"mod_log_sql: cookie string: %s", cookie_query);
1321 }
1322
1323
1324 /* Set up the actual INSERT statement */
1325 access_query = apr_psprintf(r->pool, "insert %s into %s (%s) values (%s)",
1326 /*global_config.insertdelayed?"delayed":*/"", transfer_tablename, fields, values);
1327
1328 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"mod_log_sql: access string: %s", access_query);
1329
1330 /* If the person activated force-preserve, go ahead and push all the entries
1331 * into the preserve file, then return.
1332 */
1333 if (global_config.forcepreserve) {
1334 log_error(APLOG_MARK,APLOG_DEBUG,0, orig->server,"mod_log_sql: preservation forced");
1335 preserve_entry(orig, access_query);
1336 if ( note_query != NULL )
1337 preserve_entry(orig, note_query);
1338 if ( hin_query != NULL )
1339 preserve_entry(orig, hin_query);
1340 if ( hout_query != NULL )
1341 preserve_entry(orig, hout_query);
1342 if ( cookie_query != NULL )
1343 preserve_entry(orig, cookie_query);
1344 return OK;
1345 }
1346
1347 /* How's our mysql link integrity? */
1348 if (!global_config.db.connected) {
1349 if (!global_config.forcepreserve) {
1350 /* Make a try to establish the link */
1351 log_sql_opendb_link(r->server);
1352 }
1353 if (!global_config.db.connected) {
1354 /* Unable to re-establish a DB link, so assume that it's really
1355 * gone and send the entry to the preserve file instead.
1356 * This short-circuits safe_sql_query() during a db outage and therefore
1357 * we don't keep logging the db error over and over.
1358 */
1359 preserve_entry(orig, access_query);
1360 if ( note_query != NULL )
1361 preserve_entry(orig, note_query);
1362 if ( hin_query != NULL )
1363 preserve_entry(orig, hin_query);
1364 if ( hout_query != NULL )
1365 preserve_entry(orig, hout_query);
1366 if ( cookie_query != NULL )
1367 preserve_entry(orig, cookie_query);
1368
1369 return OK;
1370 } else {
1371 /* Whew, we got the DB link back */
1372 log_error(APLOG_MARK,APLOG_NOTICE,0, orig->server,"mod_log_sql: child established database connection");
1373 }
1374 }
1375
1376
1377 /* ---> So as of here we have a non-null value of mysql_log. <--- */
1378 /* ---> i.e. we have a good MySQL connection. <--- */
1379
1380 /* Make the access-table insert */
1381 safe_sql_insert(orig,LOGSQL_TABLE_ACCESS,transfer_tablename,access_query);
1382
1383 /* Log the optional notes, headers, etc. */
1384 if (note_query)
1385 safe_sql_insert(orig, LOGSQL_TABLE_NOTES,notes_tablename,note_query);
1386
1387 if (hout_query)
1388 safe_sql_insert(orig, LOGSQL_TABLE_HEADERSOUT,hout_tablename,hout_query);
1389
1390 if (hin_query)
1391 safe_sql_insert(orig, LOGSQL_TABLE_HEADERSIN,hin_tablename,hin_query);
1392
1393 if (cookie_query)
1394 safe_sql_insert(orig, LOGSQL_TABLE_COOKIES,cookie_tablename,cookie_query);
1395
1396 return OK;
1397 }
1398}
1399
1400
1401/* Setup of the available httpd.conf configuration commands.
1402 * Structure: command, function called, NULL, where available, how many arguments, verbose description
1403 */
1404static const command_rec log_sql_cmds[] = {
1405 AP_INIT_FLAG("LogSQLAnnounce", set_global_flag_slot,
1406 (void *)APR_OFFSETOF(global_config_t, announce), RSRC_CONF,
1407 "Whether to announce that mod_log_sql is loaded in the server header")
1408 ,
1409 /* DB connection parameters */
1410 AP_INIT_TAKE13("LogSQLLoginInfo", set_log_sql_info, NULL, RSRC_CONF,
1411 "The database connection URI in the form &quot;driver://user:password@hostname:port/database&quot;")
1412 ,
1413 AP_INIT_TAKE2("LogSQLDBParam", set_dbparam, NULL, RSRC_CONF,
1414 "First argument is the DB parameter, second is the value to assign")
1415 ,
1416 AP_INIT_FLAG("LogSQLForcePreserve", set_global_flag_slot,
1417 (void *)APR_OFFSETOF(global_config_t, forcepreserve), RSRC_CONF,
1418 "Forces logging to preserve file and bypasses database")
1419 ,
1420 AP_INIT_FLAG("LogSQLDisablePreserve", set_global_flag_slot,
1421 (void *)APR_OFFSETOF(global_config_t, disablepreserve), RSRC_CONF,
1422 "Completely disables use of the preserve file")
1423 ,
1424 AP_INIT_TAKE1("LogSQLPreserveFile", set_server_file_slot,
1425 (void *)APR_OFFSETOF(logsql_state,preserve_file), RSRC_CONF,
1426 "Name of the file to use for data preservation during database downtime")
1427 ,
1428 AP_INIT_FLAG("LogSQLCreateTables", set_global_nmv_flag_slot,
1429 (void *)APR_OFFSETOF(global_config_t, createtables), RSRC_CONF,
1430 "Turn on module's capability to create its SQL tables on the fly")
1431 ,
1432 /* Table names */
1433 AP_INIT_FLAG("LogSQLMassVirtualHosting", set_global_flag_slot,
1434 (void *)APR_OFFSETOF(global_config_t, massvirtual), RSRC_CONF,
1435 "Activates option(s) useful for ISPs performing mass virutal hosting")
1436 ,
1437 AP_INIT_TAKE1("LogSQLTransferLogTable", set_server_nmv_string_slot,
1438 (void *)APR_OFFSETOF(logsql_state, transfer_table_name), RSRC_CONF,
1439 "The database table that holds the transfer log")
1440 ,
1441 AP_INIT_TAKE1("LogSQLNotesLogTable", set_server_nmv_string_slot,
1442 (void *)APR_OFFSETOF(logsql_state, notes_table_name), RSRC_CONF,
1443 "The database table that holds the notes")
1444 ,
1445 AP_INIT_TAKE1("LogSQLHeadersOutLogTable", set_server_nmv_string_slot,
1446 (void *)APR_OFFSETOF(logsql_state, hout_table_name), RSRC_CONF,
1447 "The database table that holds the outbound headers")
1448 ,
1449 AP_INIT_TAKE1("LogSQLHeadersInLogTable", set_server_nmv_string_slot,
1450 (void *)APR_OFFSETOF(logsql_state, hin_table_name), RSRC_CONF,
1451 "The database table that holds the inbound headers")
1452 ,
1453 AP_INIT_TAKE1("LogSQLCookieLogTable", set_server_nmv_string_slot,
1454 (void *)APR_OFFSETOF(logsql_state, cookie_table_name), RSRC_CONF,
1455 "The database table that holds the cookie info")
1456 ,
1457 /* New Log Format */
1458 AP_INIT_ITERATE("LogSQLTransferLogItems", add_server_string_slot,
1459 (void *)APR_OFFSETOF(logsql_state, transfer_log_format), RSRC_CONF,
1460 "What fields to log to the database transfer log")
1461 ,
1462 AP_INIT_RAW_ARGS("LogSQLRegisterItem", set_register_field,
1463 NULL, RSRC_CONF,
1464 "Register a new Item for logging, Arguments: ItemName function argument sqlfield datatype datalen<br>"
1465 "datatypes are INT, SMALLINT, VARCHAR, CHAR<br>")
1466 ,
1467 AP_INIT_TAKE1("LogSQLShowConfig", set_global_string_slot,
1468 (void *)APR_OFFSETOF(global_config_t, showconfig), RSRC_CONF,
1469 "Add this to export the entire running function and dfield configuration to the named file")
1470 ,
1471 /* Machine ID */
1472 AP_INIT_TAKE1("LogSQLMachineID", set_global_string_slot,
1473 (void *)APR_OFFSETOF(global_config_t, machid), RSRC_CONF,
1474 "Machine ID that the module will log, useful in web clusters to differentiate machines")
1475 ,
1476 /* Limits on logging */
1477 AP_INIT_ITERATE("LogSQLRequestAccept", add_server_string_slot,
1478 (void *)APR_OFFSETOF(logsql_state, transfer_accept_list), RSRC_CONF,
1479 "List of URIs to accept for logging. Accesses that don't match will not be logged")
1480 ,
1481 AP_INIT_ITERATE("LogSQLRequestIgnore", add_server_string_slot,
1482 (void *)APR_OFFSETOF(logsql_state, transfer_ignore_list), RSRC_CONF,
1483 "List of URIs to ignore. Accesses that match will not be logged to database")
1484 ,
1485 AP_INIT_ITERATE("LogSQLRemhostIgnore", add_server_string_slot,
1486 (void *)APR_OFFSETOF(logsql_state, remhost_ignore_list), RSRC_CONF,
1487 "List of remote hosts to ignore. Accesses that match will not be logged to database")
1488 ,
1489 /* Special logging table configuration */
1490 AP_INIT_TAKE1("LogSQLWhichCookie", set_server_string_slot,
1491 (void *)APR_OFFSETOF(logsql_state, cookie_name), RSRC_CONF,
1492 "The single cookie that you want logged in the access_log when using the 'c' config directive")
1493 ,
1494 AP_INIT_ITERATE("LogSQLWhichNotes", add_server_string_slot,
1495 (void *)APR_OFFSETOF(logsql_state, notes_list), RSRC_CONF,
1496 "Notes that you would like to log in a separate table")
1497 ,
1498 AP_INIT_ITERATE("LogSQLWhichHeadersOut", add_server_string_slot,
1499 (void *)APR_OFFSETOF(logsql_state, hout_list), RSRC_CONF,
1500 "Outbound headers that you would like to log in a separate table")
1501 ,
1502 AP_INIT_ITERATE("LogSQLWhichHeadersIn", add_server_string_slot,
1503 (void *)APR_OFFSETOF(logsql_state, hin_list), RSRC_CONF,
1504 "Inbound headers that you would like to log in a separate table")
1505 ,
1506 AP_INIT_ITERATE("LogSQLWhichCookies", add_server_string_slot,
1507 (void *)APR_OFFSETOF(logsql_state, cookie_list), RSRC_CONF,
1508 "The cookie(s) that you would like to log in a separate table")
1509 ,
1510 AP_INIT_RAW_ARGS("LogSQLDeprecated", ap_set_deprecated, NULL, RSRC_CONF,
1511 "<br><b>Deprecated</b><br>The following Commands are deprecated and should not be used.. <br>Read the documentation for more information<br><b>Deprecated</b>")
1512 ,
1513 /* Deprecated commands */
1514 AP_INIT_TAKE1("LogSQLTransferLogFormat", set_logformat_slot,
1515 (void *)APR_OFFSETOF(logsql_state, transfer_log_format), RSRC_CONF,
1516 "<b>(Deprecated) Use LogSQLTransferLogItem to specify symbolic log items instead")
1517 ,
1518 AP_INIT_TAKE1("LogSQLDatabase", set_dbparam_slot,
1519 (void *)"database", RSRC_CONF,
1520 "<b>(Deprecated) Use LogSQLDBParam database dbname.</b> The name of the database database for logging")
1521 ,
1522 AP_INIT_TAKE1("LogSQLTableType", set_dbparam_slot,
1523 (void *)"tabletype", RSRC_CONF,
1524 "<b>(Deprecated) Use LogSQLDBParam tabletype type.</b> What kind of table to create (MyISAM, InnoDB,...) when creating tables")
1525 ,
1526 AP_INIT_TAKE1("LogSQLSocketFile", set_dbparam_slot,
1527 (void *)"socketfile", RSRC_CONF,
1528 "<b>(Deprecated) Use LogSQLDBParam socketfile socket.</b> Name of the file to employ for socket connections to database")
1529 ,
1530 AP_INIT_TAKE1("LogSQLTCPPort", set_dbparam_slot,
1531 (void *)"port", RSRC_CONF,
1532 "<b>(Deprecated) Use LogSQLDBParam port port.</b> Port number to use for TCP connections to database, defaults to 3306 if not set")
1533 ,
1534 {NULL}
1535};
1536/* The configuration array that sets up the hooks into the module. */
1537#if defined(WITH_APACHE20)
1538static void register_hooks(apr_pool_t *p) {
1539 ap_hook_post_config(log_sql_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
1540 ap_hook_child_init(log_sql_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1541 ap_hook_log_transaction(log_sql_transaction, NULL, NULL, APR_HOOK_MIDDLE);
1542}
1543
1544module AP_MODULE_DECLARE_DATA log_sql_module = {
1545 STANDARD20_MODULE_STUFF,
1546 NULL, /* create per-directory config structures */
1547 NULL, /* merge per-directory config structures */
1548 log_sql_make_state, /* create per-server config structures */
1549 log_sql_merge_state, /* merge per-server config structures */
1550 log_sql_cmds, /* command handlers */
1551 register_hooks /* register hooks */
1552};
1553#elif defined(WITH_APACHE13)
1554module MODULE_VAR_EXPORT log_sql_module = {
1555 STANDARD_MODULE_STUFF,
1556 log_sql_module_init, /* module initializer */
1557 NULL, /* create per-dir config */
1558 NULL, /* merge per-dir config */
1559 log_sql_make_state, /* create server config */
1560 log_sql_merge_state, /* merge server config */
1561 log_sql_cmds, /* config directive table */
1562 NULL, /* [9] content handlers */
1563 NULL, /* [2] URI-to-filename translation */
1564 NULL, /* [5] check/validate user_id */
1565 NULL, /* [6] check authorization */
1566 NULL, /* [4] check access by host */
1567 NULL, /* [7] MIME type checker/setter */
1568 NULL, /* [8] fixups */
1569 log_sql_transaction, /* [10] logger */
1570 NULL /* [3] header parser */
1571#if MODULE_MAGIC_NUMBER >= 19970728 /* 1.3-dev or later support these additionals... */
1572 ,log_sql_child_init, /* child process initializer */
1573 log_sql_child_exit, /* process exit/cleanup */
1574 NULL /* [1] post read-request */
1575#endif
1576
1577};
1578#endif
diff --git a/src/mod_log_sql_dbd.c b/src/mod_log_sql_dbd.c
new file mode 100644
index 0000000..e641c35
--- /dev/null
+++ b/src/mod_log_sql_dbd.c
@@ -0,0 +1,132 @@
1/* $Id: mod_log_sql_dbi.c 120 2004-04-17 15:14:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#else
6# error Unsupported Apache version
7#endif
8
9
10#ifdef HAVE_CONFIG_H
11/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
12 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
13 */
14#undef PACKAGE_BUGREPORT
15#undef PACKAGE_NAME
16#undef PACKAGE_STRING
17#undef PACKAGE_TARNAME
18#undef PACKAGE_VERSION
19
20#include "autoconfig.h"
21#endif
22
23#include "mod_log_sql.h"
24
25#include "apr_dbd.h"
26#include "mod_dbd.h"
27
28typedef struct {
29 ap_dbd_t *dbd;
30} request_config_t;
31
32LOGSQL_MODULE_FORWARD(dbd);
33
34static ap_dbd_t *(*dbd_acquire_fn)(request_rec*) = NULL;
35
36static ap_dbd_t *log_sql_dbd_getconnection(request_rec *r)
37{
38 request_config_t *rconf = ap_get_module_config(r->request_config, &LOGSQL_MODULE(dbd));
39 if (!rconf) {
40 rconf = apr_pcalloc(r->pool, sizeof(request_config_t));
41 ap_set_module_config(r->request_config, &LOGSQL_MODULE(dbd), (void *)rconf);
42 rconf->dbd = dbd_acquire_fn(r);
43 }
44 return rconf->dbd;
45}
46
47/* Connect to the database */
48static logsql_opendb_ret log_sql_dbd_connect(server_rec *s, logsql_dbconnection *db)
49{
50 // We are using mod_dbd so we don't do anything here
51 if (!dbd_acquire_fn) {
52 // no mod_dbd return failure
53 log_error(APLOG_MARK,APLOG_ERR,0, s,"mod_log_sql_dbd: mod_dbd is not loaded or available");
54 return LOGSQL_OPENDB_FAIL;
55 } else {
56 return LOGSQL_OPENDB_SUCCESS;
57 }
58}
59
60/* Close the DB link */
61static void log_sql_dbd_close(logsql_dbconnection *db)
62{
63 // mod_dbd handles this, so do nothing
64}
65
66/* Routine to escape the 'dangerous' characters that would otherwise
67 * corrupt the INSERT string: ', \, and "
68 */
69static const char *log_sql_dbd_escape(request_rec *r, const char *from_str, apr_pool_t *p,
70 logsql_dbconnection *db)
71{
72 // Acquire a DBD connection from mod_dbd
73 ap_dbd_t *dbd = log_sql_dbd_getconnection(r);
74 if (!dbd) return NULL;
75
76 if (!from_str)
77 return NULL;
78
79 return apr_pstrcat(p, "'",apr_dbd_escape(dbd->driver, p, from_str, dbd->handle),"'",NULL);
80}
81
82/* Run an insert query and return a categorized error or success */
83static logsql_query_ret log_sql_dbd_query(request_rec *r,logsql_dbconnection *db,
84 const char *query)
85{
86 int ret;
87 const char *err;
88 int affected;
89 // Acquire a DBD connection from mod_dbd
90 ap_dbd_t *dbd = log_sql_dbd_getconnection(r);
91 if (!dbd) return LOGSQL_QUERY_NOLINK;
92
93 // Run the query
94 ret = apr_dbd_query(dbd->driver, dbd->handle, &affected, query);
95 if (ret == 0) {
96 return LOGSQL_QUERY_SUCCESS;
97 } else {
98 // attempt to detect error message
99 err = apr_dbd_error(dbd->driver, dbd->handle, ret);
100 log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "DB Returned error: (%d) %s", ret, err);
101 // Unable to check if "NO SUCH TABLE" due to apr_dbd not mapping error codes to a standard set.
102 return LOGSQL_QUERY_FAIL;
103 }
104}
105
106/* Create table table_name of type table_type. */
107static logsql_table_ret log_sql_dbd_create(request_rec *r, logsql_dbconnection *db,
108 logsql_tabletype table_type, const char *table_name)
109{
110 return LOGSQL_TABLE_FAIL;
111}
112
113static const char *supported_drivers[] = {"dbd",NULL};
114static logsql_dbdriver log_sql_dbd_driver = {
115 "dbd",
116 supported_drivers,
117 log_sql_dbd_connect,/* open DB connection */
118 log_sql_dbd_close, /* close DB connection */
119 log_sql_dbd_escape, /* escape query */
120 log_sql_dbd_query, /* insert query */
121 log_sql_dbd_create /* create table */
122};
123
124LOGSQL_REGISTER(dbd) {
125 dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
126 if (dbd_acquire_fn == NULL) {
127 log_error(APLOG_MARK,APLOG_ERR,0,s,"You must load mod_dbd to enable AuthDBD functions");
128 }
129
130 log_sql_register_driver(p,&log_sql_dbd_driver);
131 LOGSQL_REGISTER_RETURN;
132}
diff --git a/src/mod_log_sql_dbi.c b/src/mod_log_sql_dbi.c
new file mode 100644
index 0000000..5b83836
--- /dev/null
+++ b/src/mod_log_sql_dbi.c
@@ -0,0 +1,263 @@
1/* $Id: mod_log_sql_dbi.c 120 2004-04-17 15:14:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#elif defined(WITH_APACHE13)
6# include "apache13.h"
7#else
8# error Unsupported Apache version
9#endif
10
11
12#ifdef HAVE_CONFIG_H
13/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
14 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
15 */
16#undef PACKAGE_BUGREPORT
17#undef PACKAGE_NAME
18#undef PACKAGE_STRING
19#undef PACKAGE_TARNAME
20#undef PACKAGE_VERSION
21
22#include "autoconfig.h"
23#endif
24
25#include "mod_log_sql.h"
26
27#include "dbi/dbi.h"
28
29typedef struct {
30 dbi_conn conn;
31} dbi_conn_rec;
32
33/* Connect to the MYSQL database */
34static logsql_opendb_ret log_sql_dbi_connect(server_rec *s, logsql_dbconnection *db)
35{
36 const char *driver = apr_table_get(db->parms,"driver");
37 const char *host = apr_table_get(db->parms,"hostname");
38 const char *user = apr_table_get(db->parms,"username");
39 const char *passwd = apr_table_get(db->parms,"password");
40 const char *database = apr_table_get(db->parms,"database");
41 const char *s_tcpport = apr_table_get(db->parms,"port");
42 unsigned int tcpport = (s_tcpport)?atoi(s_tcpport):0;
43 const char *socketfile = apr_table_get(db->parms,"socketfile");
44 //dbi_result result;
45 dbi_conn_rec *dblink = db->handle;
46 if (!dblink) {
47 dblink = apr_pcalloc(db->p, sizeof(*dblink));
48 db->handle = (void *)dblink;
49 }
50
51 dblink->conn = dbi_conn_new(driver);
52
53 dbi_conn_set_option(dblink->conn, "host", host);
54 dbi_conn_set_option(dblink->conn, "username", user);
55 dbi_conn_set_option(dblink->conn, "password", passwd);
56 dbi_conn_set_option(dblink->conn, "dbname", database);
57 if (tcpport) {
58 dbi_conn_set_option_numeric(dblink->conn, "port", tcpport);
59 }
60
61 if (socketfile && !strcmp(driver,"mysql")) {
62 dbi_conn_set_option(dblink->conn, "mysql_unix_socket", socketfile);
63 }
64
65 if (!dbi_conn_connect(dblink->conn)) {
66 log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
67 "HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
68 host, tcpport, database, user, socketfile);
69 return LOGSQL_OPENDB_SUCCESS;
70 } else {
71 const char *error;
72 dbi_conn_error(dblink->conn, &error);
73 log_error(APLOG_MARK, APLOG_ERR, 0, s,
74 "DBI Error: %s", error);
75 log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
76 "HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
77 host, tcpport, database, user, socketfile);
78 return LOGSQL_OPENDB_FAIL;
79 }
80}
81
82/* Close the DB link */
83static void log_sql_dbi_close(logsql_dbconnection *db)
84{
85 dbi_conn_rec *dblink = db->handle;
86 dbi_conn_close(dblink->conn);
87 dblink->conn = NULL;
88}
89
90/* Routine to escape the 'dangerous' characters that would otherwise
91 * corrupt the INSERT string: ', \, and "
92 */
93static const char *log_sql_dbi_escape(request_rec *r,const char *from_str, apr_pool_t *p,
94 logsql_dbconnection *db)
95{
96 dbi_conn_rec *dblink = db->handle;
97
98 if (!from_str)
99 return NULL;
100 else {
101 char *to_str = strdup(from_str);
102 char *retval;
103 dbi_driver_quote_string(dbi_conn_get_driver(dblink->conn), &to_str);
104 retval = apr_pstrdup(p, to_str);
105 free(to_str);
106 return retval;
107 }
108}
109
110/* Run a mysql insert query and return a categorized error or success */
111static logsql_query_ret log_sql_dbi_query(request_rec *r,logsql_dbconnection *db,
112 const char *query)
113{
114 const char *error;
115 dbi_result result;
116 dbi_conn_rec *dblink = db->handle;
117
118 if (!dblink->conn) {
119 return LOGSQL_QUERY_NOLINK;
120 }
121
122 /* Run the query */
123 if ((result = dbi_conn_query(dblink->conn, query))) {
124 return LOGSQL_QUERY_SUCCESS;
125 }
126 /* Check to see if the error is "nonexistent table" */
127 dbi_conn_error(dblink->conn, &error);
128 log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
129 "DBI Error: %s", error);
130/* if (real_error == ER_NO_SUCH_TABLE) {
131 log_error(APLOG_MARK,APLOG_ERR,0, r->server,"table does not exist, preserving query");
132 return LOGSQL_QUERY_NOTABLE;
133 }*/
134 return LOGSQL_QUERY_FAIL;
135}
136
137/* Create table table_name of type table_type. */
138static logsql_table_ret log_sql_dbi_create(request_rec *r, logsql_dbconnection *db,
139 logsql_tabletype table_type, const char *table_name)
140{
141 dbi_result result;
142 const char *driver = apr_table_get(db->parms,"driver");
143 const char *tabletype = apr_table_get(db->parms,"tabletype");
144 char *type_suffix = NULL;
145
146 char *create_prefix = "create table if not exists `";
147 char *create_suffix = NULL;
148 char *create_sql;
149
150 dbi_conn_rec *dblink = db->handle;
151
152/* if (!global_config.createtables) {
153 return APR_SUCCESS;
154 }*/
155
156 switch (table_type) {
157 case LOGSQL_TABLE_ACCESS:
158 create_suffix =
159 "` (id char(19),\
160 agent varchar(255),\
161 bytes_sent int unsigned,\
162 child_pid smallint unsigned,\
163 cookie varchar(255),\
164 machine_id varchar(25),\
165 request_file varchar(255),\
166 referer varchar(255),\
167 remote_host varchar(50),\
168 remote_logname varchar(50),\
169 remote_user varchar(50),\
170 request_duration smallint unsigned,\
171 request_line varchar(255),\
172 request_method varchar(10),\
173 request_protocol varchar(10),\
174 request_time char(28),\
175 request_uri varchar(255),\
176 request_args varchar(255),\
177 server_port smallint unsigned,\
178 ssl_cipher varchar(25),\
179 ssl_keysize smallint unsigned,\
180 ssl_maxkeysize smallint unsigned,\
181 status smallint unsigned,\
182 time_stamp int unsigned,\
183 virtual_host varchar(255))";
184 break;
185 case LOGSQL_TABLE_COOKIES:
186 case LOGSQL_TABLE_HEADERSIN:
187 case LOGSQL_TABLE_HEADERSOUT:
188 case LOGSQL_TABLE_NOTES:
189 create_suffix =
190 "` (id char(19),\
191 item varchar(80),\
192 val varchar(80))";
193 break;
194 }
195
196 if (tabletype && !strcmp(driver,"mysql")) {
197 type_suffix = apr_pstrcat(r->pool, " TYPE=",
198 tabletype, NULL);
199 }
200 /* Find memory long enough to hold the whole CREATE string + \0 */
201 create_sql = apr_pstrcat(r->pool, create_prefix, table_name, create_suffix,
202 type_suffix, NULL);
203
204 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"create string: %s", create_sql);
205
206 if (!dblink) {
207 return LOGSQL_QUERY_NOLINK;
208 }
209
210 /* Run the create query */
211 if (!(result = dbi_conn_query(dblink, create_sql))) {
212 const char *error;
213 dbi_conn_error(dblink->conn, &error);
214 log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
215 "DBI Error: %s", error);
216 return LOGSQL_TABLE_FAIL;
217 }
218
219 return LOGSQL_TABLE_SUCCESS;
220}
221
222static logsql_dbdriver log_sql_dbi_driver = {
223 "dbi",
224 NULL,
225 log_sql_dbi_connect, /* open DB connection */
226 log_sql_dbi_close, /* close DB connection */
227 log_sql_dbi_escape, /* escape query */
228 log_sql_dbi_query, /* insert query */
229 log_sql_dbi_create /* create table */
230};
231
232static apr_status_t log_sql_dbi_cleanup(void *data)
233{
234 dbi_shutdown();
235#if defined(WITH_APACHE20)
236 return APR_SUCCESS;
237#endif
238}
239
240LOGSQL_REGISTER(dbi) {
241 dbi_driver driver;
242 const char **driver_list;
243 int count = 1;
244
245 dbi_initialize(NULL);
246
247 for (driver = dbi_driver_list(NULL);
248 driver;
249 driver = dbi_driver_list(driver)) {
250 count++;
251 }
252 driver_list = apr_pcalloc(p, sizeof(char *) * (count));
253 count = 0;
254 for (driver = dbi_driver_list(NULL);
255 driver;
256 driver = dbi_driver_list(driver)) {
257 driver_list[count++] = dbi_driver_get_name(driver);
258 }
259 log_sql_dbi_driver.provided_drivers = driver_list;
260 log_sql_register_driver(p,&log_sql_dbi_driver);
261 apr_pool_cleanup_register(p, NULL, log_sql_dbi_cleanup, NULL);
262 LOGSQL_REGISTER_RETURN;
263}
diff --git a/src/mod_log_sql_logio.c b/src/mod_log_sql_logio.c
new file mode 100644
index 0000000..ebdeddd
--- /dev/null
+++ b/src/mod_log_sql_logio.c
@@ -0,0 +1,146 @@
1/* $Id: mod_log_sql_ssl.c 140 2004-05-14 03:50:47Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#else
6# error Unsupported Apache version
7#endif
8
9#ifdef HAVE_CONFIG_H
10/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
11 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
12 */
13#undef PACKAGE_BUGREPORT
14#undef PACKAGE_NAME
15#undef PACKAGE_STRING
16#undef PACKAGE_TARNAME
17#undef PACKAGE_VERSION
18
19#include "autoconfig.h"
20#endif
21
22#include "mod_log_sql.h"
23
24#include "http_connection.h"
25
26module AP_MODULE_DECLARE_DATA log_sql_logio_module;
27
28// From apachge 2.2's mod_logio.c to provide logging ACTUAL incoming and outgoing bytes
29static const char logio_filter_name[] = "LOG_SQL_INPUT_OUTPUT";
30
31typedef struct {
32 apr_off_t bytes_in;
33 apr_off_t bytes_out;
34} logio_config_t;
35
36static void ap_logio_add_bytes_out(conn_rec *c, apr_off_t bytes){
37 logio_config_t *cf = ap_get_module_config(c->conn_config, &log_sql_logio_module);
38
39 cf->bytes_out += bytes;
40}
41
42static const char *log_bytes_in(request_rec *r, char *a)
43{
44 logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
45 &log_sql_logio_module);
46
47 return apr_off_t_toa(r->pool, cf->bytes_in);
48}
49
50static const char *log_bytes_out(request_rec *r, char *a)
51{
52 logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
53 &log_sql_logio_module);
54
55 return apr_off_t_toa(r->pool, cf->bytes_out);
56}
57
58static int logio_transaction(request_rec *r)
59{
60 logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
61 &log_sql_logio_module);
62
63 cf->bytes_in = cf->bytes_out = 0;
64
65 return OK;
66}
67
68static apr_status_t logio_in_filter(ap_filter_t *f,
69 apr_bucket_brigade *bb,
70 ap_input_mode_t mode,
71 apr_read_type_e block,
72 apr_off_t readbytes) {
73 apr_off_t length;
74 apr_status_t status;
75 logio_config_t *cf = ap_get_module_config(f->c->conn_config, &log_sql_logio_module);
76
77 status = ap_get_brigade(f->next, bb, mode, block, readbytes);
78
79 apr_brigade_length (bb, 0, &length);
80
81 if (length > 0)
82 cf->bytes_in += length;
83
84 return status;
85}
86
87static apr_status_t logio_out_filter(ap_filter_t *f,
88 apr_bucket_brigade *bb) {
89 apr_bucket *b = APR_BRIGADE_LAST(bb);
90
91 /* End of data, make sure we flush */
92 if (APR_BUCKET_IS_EOS(b)) {
93 APR_BUCKET_INSERT_BEFORE(b,
94 apr_bucket_flush_create(f->c->bucket_alloc));
95 }
96
97 return ap_pass_brigade(f->next, bb);
98}
99
100static int logio_pre_conn(conn_rec *c, void *csd) {
101 logio_config_t *cf = apr_pcalloc(c->pool, sizeof(logio_config_t));
102
103 ap_set_module_config(c->conn_config, &log_sql_logio_module, cf);
104
105 ap_add_input_filter(logio_filter_name, NULL, NULL, c);
106 ap_add_output_filter(logio_filter_name, NULL, NULL, c);
107
108 return OK;
109}
110
111static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
112{
113 log_sql_register_function(p, "bytes_in", log_bytes_in, LOGSQL_FUNCTION_REQ_FINAL);
114 log_sql_register_function(p, "bytes_out", log_bytes_out, LOGSQL_FUNCTION_REQ_FINAL);
115
116 log_sql_register_alias(s,p,'i', "bytes_in");
117 log_sql_register_alias(s,p,'o', "bytes_out");
118
119 log_sql_register_field(p, "bytes_in", "bytes_in", NULL,
120 "bytes_in", LOGSQL_DATATYPE_INT, 0);
121 log_sql_register_field(p, "bytes_out", "bytes_out", NULL,
122 "bytes_out", LOGSQL_DATATYPE_INT, 0);
123
124 log_sql_register_finish(s);
125 return OK;
126}
127
128static void register_hooks(apr_pool_t *p) {
129 static const char *pre[] = { "mod_log_sql.c", NULL };
130
131 ap_hook_pre_connection(logio_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
132 ap_hook_log_transaction(logio_transaction, pre, NULL, APR_HOOK_MIDDLE);
133 ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
134
135 ap_register_input_filter(logio_filter_name, logio_in_filter, NULL,
136 AP_FTYPE_NETWORK - 1);
137 ap_register_output_filter(logio_filter_name, logio_out_filter, NULL,
138 AP_FTYPE_NETWORK - 1);
139
140 APR_REGISTER_OPTIONAL_FN(ap_logio_add_bytes_out);
141}
142
143module AP_MODULE_DECLARE_DATA log_sql_logio_module = {
144 STANDARD20_MODULE_STUFF,
145 NULL, NULL, NULL, NULL, NULL, register_hooks
146};
diff --git a/src/mod_log_sql_mysql.c b/src/mod_log_sql_mysql.c
new file mode 100644
index 0000000..902cadf
--- /dev/null
+++ b/src/mod_log_sql_mysql.c
@@ -0,0 +1,269 @@
1/* $Id:mod_log_sql_mysql.c 180 2008-09-21 15:54:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#elif defined(WITH_APACHE13)
6# include "apache13.h"
7#else
8# error Unsupported Apache version
9#endif
10
11#ifdef HAVE_CONFIG_H
12/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
13 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
14 */
15#undef PACKAGE_BUGREPORT
16#undef PACKAGE_NAME
17#undef PACKAGE_STRING
18#undef PACKAGE_TARNAME
19#undef PACKAGE_VERSION
20
21#include "autoconfig.h"
22#endif
23
24#include "mod_log_sql.h"
25
26#include "mysql.h"
27#include "mysqld_error.h"
28
29/* The enduser won't modify these */
30#define MYSQL_ERROR(mysql) ((mysql)?(mysql_error(mysql)):"MySQL server has gone away")
31
32/* Connect to the MYSQL database */
33static logsql_opendb_ret log_sql_mysql_connect(server_rec *s, logsql_dbconnection *db)
34{
35 const char *host = apr_table_get(db->parms,"hostname");
36 const char *user = apr_table_get(db->parms,"username");
37 const char *passwd = apr_table_get(db->parms,"password");
38 const char *database = apr_table_get(db->parms,"database");
39 const char *s_tcpport = apr_table_get(db->parms,"port");
40 unsigned int tcpport = (s_tcpport)?atoi(s_tcpport):3306;
41 const char *socketfile = apr_table_get(db->parms,"socketfile");
42 MYSQL *dblink = db->handle;
43
44 dblink = mysql_init(dblink);
45 db->handle = (void *)dblink;
46
47
48 if (!socketfile) {
49 socketfile = "/var/lib/mysql/mysql.sock";
50 }
51
52 if (mysql_real_connect(dblink, host, user, passwd, database, tcpport,
53 socketfile, 0)) {
54 log_error(APLOG_MARK,APLOG_DEBUG,0, s,"HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
55 host, tcpport, database, user, socketfile);
56 return LOGSQL_OPENDB_SUCCESS;
57 } else {
58 log_error(APLOG_MARK,APLOG_ERR,0, s,"mod_log_sql_mysql: database connection error: mysql error: %s",
59 MYSQL_ERROR(dblink));
60 log_error(APLOG_MARK,APLOG_DEBUG, 0, s,"HOST: '%s' PORT: '%d' DB: '%s' USER: '%s' SOCKET: '%s'",
61 host, tcpport, database, user, socketfile);
62 return LOGSQL_OPENDB_FAIL;
63 }
64}
65
66/* Close the DB link */
67static void log_sql_mysql_close(logsql_dbconnection *db)
68{
69 mysql_close((MYSQL *)db->handle);
70 /* mysql_close frees this data so NULL it out incase we reconnect later */
71 db->handle=NULL;
72}
73
74/* Routine to escape the 'dangerous' characters that would otherwise
75 * corrupt the INSERT string: ', \, and "
76 */
77static const char *log_sql_mysql_escape(request_rec *r, const char *from_str, apr_pool_t *p,
78 logsql_dbconnection *db)
79{
80 /* Return "NULL" for empty strings */
81 if (!from_str || strlen(from_str) == 0)
82 return "NULL";
83 else {
84 char *to_str;
85 unsigned long length = strlen(from_str);
86 unsigned long retval;
87
88 /* Pre-allocate a new string that could hold twice the original, which would only
89 * happen if the whole original string was 'dangerous' characters.
90 */
91 to_str = (char *) apr_palloc(p, length * 2 + 3);
92 if (!to_str) {
93 return from_str;
94 }
95 strcpy(to_str, "'");
96 if (!db->connected) {
97 /* Well, I would have liked to use the current database charset. mysql is
98 * unavailable, however, so I fall back to the slightly less respectful
99 * mysql_escape_string() function that uses the default charset.
100 */
101 retval = mysql_escape_string(to_str+1, from_str, length);
102 } else {
103 /* MySQL is available, so I'll go ahead and respect the current charset when
104 * I perform the escape.
105 */
106 retval = mysql_real_escape_string((MYSQL *)db->handle, to_str+1, from_str, length);
107 }
108 strcat(to_str,"'");
109
110 if (retval)
111 return to_str;
112 else
113 return from_str;
114 }
115}
116
117#if defined(WIN32)
118#define SIGNAL_GRAB
119#define SIGNAL_RELEASE
120#define SIGNAL_VAR
121#else
122#define SIGNAL_VAR void (*handler) (int);
123#define SIGNAL_GRAB handler = signal(SIGPIPE, SIG_IGN);
124#define SIGNAL_RELEASE signal(SIGPIPE, handler);
125#endif
126/* Run a mysql insert query and return a categorized error or success */
127static logsql_query_ret log_sql_mysql_query(request_rec *r,logsql_dbconnection *db,
128 const char *query)
129{
130 int retval;
131 SIGNAL_VAR
132
133 unsigned int real_error = 0;
134 /*const char *real_error_str = NULL;*/
135
136 MYSQL *dblink = (MYSQL *)db->handle;
137
138 if (!dblink) {
139 return LOGSQL_QUERY_NOLINK;
140 }
141
142 /* A failed mysql_query() may send a SIGPIPE, so we ignore that signal momentarily. */
143 SIGNAL_GRAB
144
145 /* Run the query */
146 if (!(retval = mysql_query(dblink, query))) {
147 SIGNAL_RELEASE
148 return LOGSQL_QUERY_SUCCESS;
149 }
150 real_error = mysql_errno(dblink);
151 log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
152 "mysql_query returned (%d) \"%s\"", real_error, MYSQL_ERROR(dblink));
153 /* Check to see if the error is "nonexistent table" */
154
155 if (real_error == ER_NO_SUCH_TABLE) {
156 log_error(APLOG_MARK,APLOG_ERR,0, r->server,"table does not exist, preserving query");
157 /* Restore SIGPIPE to its original handler function */
158 SIGNAL_RELEASE
159 return LOGSQL_QUERY_NOTABLE;
160 }
161
162 /* Restore SIGPIPE to its original handler function */
163 SIGNAL_RELEASE
164 return LOGSQL_QUERY_FAIL;
165}
166
167/* Create table table_name of type table_type. */
168static logsql_table_ret log_sql_mysql_create(request_rec *r, logsql_dbconnection *db,
169 logsql_tabletype table_type, const char *table_name)
170{
171 int retval;
172 const char *tabletype = apr_table_get(db->parms,"tabletype");
173 SIGNAL_VAR
174 char *type_suffix = NULL;
175
176 char *create_prefix = "create table if not exists `";
177 char *create_suffix = NULL;
178 char *create_sql;
179
180 MYSQL *dblink = (MYSQL *)db->handle;
181
182/* if (!global_config.createtables) {
183 return APR_SUCCESS;
184 }*/
185
186 switch (table_type) {
187 case LOGSQL_TABLE_ACCESS:
188 create_suffix =
189 "` (id char(19),\
190 agent varchar(255),\
191 bytes_sent int unsigned,\
192 child_pid smallint unsigned,\
193 cookie varchar(255),\
194 machine_id varchar(25),\
195 request_file varchar(255),\
196 referer varchar(255),\
197 remote_host varchar(50),\
198 remote_logname varchar(50),\
199 remote_user varchar(50),\
200 request_duration smallint unsigned,\
201 request_line varchar(255),\
202 request_method varchar(10),\
203 request_protocol varchar(10),\
204 request_time char(28),\
205 request_uri varchar(255),\
206 request_args varchar(255),\
207 server_port smallint unsigned,\
208 ssl_cipher varchar(25),\
209 ssl_keysize smallint unsigned,\
210 ssl_maxkeysize smallint unsigned,\
211 status smallint unsigned,\
212 time_stamp int unsigned,\
213 virtual_host varchar(255),\
214 bytes_in int unsigned,\
215 bytes_out int unsigned)";
216 break;
217 case LOGSQL_TABLE_COOKIES:
218 case LOGSQL_TABLE_HEADERSIN:
219 case LOGSQL_TABLE_HEADERSOUT:
220 case LOGSQL_TABLE_NOTES:
221 create_suffix =
222 "` (id char(19),\
223 item varchar(80),\
224 val varchar(80))";
225 break;
226 }
227
228 if (tabletype) {
229 type_suffix = apr_pstrcat(r->pool, " TYPE=",
230 tabletype, NULL);
231 }
232 /* Find memory long enough to hold the whole CREATE string + \0 */
233 create_sql = apr_pstrcat(r->pool, create_prefix, table_name, create_suffix,
234 type_suffix, NULL);
235
236 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"create string: %s", create_sql);
237
238 if (!dblink) {
239 return LOGSQL_QUERY_NOLINK;
240 }
241 /* A failed mysql_query() may send a SIGPIPE, so we ignore that signal momentarily. */
242 SIGNAL_GRAB
243
244 /* Run the create query */
245 if ((retval = mysql_query(dblink, create_sql))) {
246 log_error(APLOG_MARK,APLOG_ERR,0, r->server,"failed to create table: %s",
247 table_name);
248 SIGNAL_RELEASE
249 return LOGSQL_TABLE_FAIL;
250 }
251 SIGNAL_RELEASE
252 return LOGSQL_TABLE_SUCCESS;
253}
254
255static const char *supported_drivers[] = {"mysql",NULL};
256static logsql_dbdriver mysql_driver = {
257 "mysql",
258 supported_drivers,
259 log_sql_mysql_connect, /* open DB connection */
260 log_sql_mysql_close, /* close DB connection */
261 log_sql_mysql_escape, /* escape query */
262 log_sql_mysql_query, /* insert query */
263 log_sql_mysql_create /* create table */
264};
265
266LOGSQL_REGISTER(mysql) {
267 log_sql_register_driver(p,&mysql_driver);
268 LOGSQL_REGISTER_RETURN;
269}
diff --git a/src/mod_log_sql_pgsql.c b/src/mod_log_sql_pgsql.c
new file mode 100644
index 0000000..dcfbd73
--- /dev/null
+++ b/src/mod_log_sql_pgsql.c
@@ -0,0 +1,247 @@
1/* $Id:mod_log_sql_pgsql.c 180 2008-09-21 15:54:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#elif defined(WITH_APACHE13)
6# include "apache13.h"
7#else
8# error Unsupported Apache version
9#endif
10
11
12#ifdef HAVE_CONFIG_H
13/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
14 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
15 */
16#undef PACKAGE_BUGREPORT
17#undef PACKAGE_NAME
18#undef PACKAGE_STRING
19#undef PACKAGE_TARNAME
20#undef PACKAGE_VERSION
21
22#include "autoconfig.h"
23#endif
24
25#include "mod_log_sql.h"
26
27#include "libpq-fe.h"
28
29/* Connect to the PGSQL database */
30static logsql_opendb_ret log_sql_pgsql_connect(server_rec *s, logsql_dbconnection *db)
31{
32 const char *host = apr_table_get(db->parms,"hostname");
33 const char *user = apr_table_get(db->parms,"username");
34 const char *passwd = apr_table_get(db->parms,"password");
35 const char *database = apr_table_get(db->parms,"database");
36 const char *s_tcpport = apr_table_get(db->parms,"port");
37
38 db->handle = PQsetdbLogin(host, s_tcpport, NULL, NULL, database, user, passwd);
39
40 if (PQstatus(db->handle) == CONNECTION_OK) {
41 log_error(APLOG_MARK,APLOG_DEBUG,0, s,"HOST: '%s' PORT: '%s' DB: '%s' USER: '%s'",
42 host, s_tcpport, database, user);
43 return LOGSQL_OPENDB_SUCCESS;
44 } else {
45 log_error(APLOG_MARK,APLOG_DEBUG,0, s,"mod_log_sql: database connection error: %s",
46 PQerrorMessage(db->handle));
47 log_error(APLOG_MARK,APLOG_DEBUG, 0, s,"HOST: '%s' PORT: '%s' DB: '%s' USER: '%s'",
48 host, s_tcpport, database, user);
49 return LOGSQL_OPENDB_FAIL;
50 }
51}
52
53/* Close the DB link */
54static void log_sql_pgsql_close(logsql_dbconnection *db)
55{
56 PQfinish((PGconn*)(db->handle));
57}
58
59/* Routine to escape the 'dangerous' characters that would otherwise
60 * corrupt the INSERT string: ', \, and "
61 * Also PQescapeString does not place the ' around the string. So we have
62 * to do this manually
63 */
64static const char *log_sql_pgsql_escape(const char *from_str, apr_pool_t *p,
65 logsql_dbconnection *db)
66{
67 char *temp;
68 if (!from_str)
69 return NULL;
70 else {
71 char *to_str;
72 unsigned long length = strlen(from_str);
73 unsigned long retval;
74
75 /* Pre-allocate a new string that could hold twice the original, which would only
76 * happen if the whole original string was 'dangerous' characters.
77 * And forsee the space for the 2 '
78 */
79 temp = to_str = (char *) apr_palloc(p, length * 2 + 3);
80 if (!to_str) {
81 return from_str;
82 }
83
84 *temp = '\'';
85 temp++;
86
87 retval = PQescapeString(temp, from_str, length);
88
89 /* avoid the string to be tolong for the sql database*/
90 if (retval > 250) retval = 250;
91
92 *(temp+retval) = '\'';
93 *(temp+retval+1) = '\0';
94
95 /* We must always return the to_str, because we always need the ' added */
96// if (retval)
97 return to_str;
98// else
99// return from_str;
100 }
101}
102
103/* Run a sql insert query and return a categorized error or success */
104static logsql_query_ret log_sql_pgsql_query(request_rec *r,logsql_dbconnection *db,
105 const char *query)
106{
107 PGresult *result;
108 void (*handler) (int);
109 unsigned int real_error = 0;
110 /*const char *real_error_str = NULL;*/
111
112 PGconn *conn = db->handle;
113
114 if (PQstatus(conn) != CONNECTION_OK) {
115 return LOGSQL_QUERY_NOLINK;
116 }
117 /* A failed mysql_query() may send a SIGPIPE, so we ignore that signal momentarily. */
118 /* Does postgresql do this also ??? */
119 handler = signal(SIGPIPE, SIG_IGN);
120
121 result = PQexec(conn, query);
122 /* Run the query */
123 if (PQresultStatus(result) == PGRES_COMMAND_OK) {
124 signal(SIGPIPE, handler);
125 PQclear(result);
126 return LOGSQL_QUERY_SUCCESS;
127 }
128 /* Check to see if the error is "nonexistent table" */
129 /* removed ... don't know how ! (sorry)
130 real_error = mysql_errno(dblink);
131
132 if (real_error == ER_NO_SUCH_TABLE) {
133 log_error(APLOG_MARK,APLOG_ERR,0, r->server,"table does not exist, preserving query");
134 signal(SIGPIPE, handler);
135 PQclear(result);
136 return LOGSQL_QUERY_NOTABLE;
137 }*/
138
139 /* Restore SIGPIPE to its original handler function */
140 signal(SIGPIPE, handler);
141 PQclear(result);
142 return LOGSQL_QUERY_FAIL;
143}
144
145/* Create table table_name of type table_type. */
146static logsql_table_ret log_sql_pgsql_create(request_rec *r, logsql_dbconnection *db,
147 logsql_tabletype table_type, const char *table_name)
148{
149 PGresult *result;
150 const char *tabletype = apr_table_get(db->parms,"tabletype");
151 void (*handler) (int);
152 char *type_suffix = NULL;
153
154 char *create_prefix = "create table if not exists `";
155 char *create_suffix = NULL;
156 char *create_sql;
157
158 PGconn *conn = db->handle;
159
160/* if (!global_config.createtables) {
161 return APR_SUCCESS;
162 }*/
163
164 switch (table_type) {
165 case LOGSQL_TABLE_ACCESS:
166 create_suffix =
167 "` (id char(19),\
168 agent varchar(255),\
169 bytes_sent int unsigned,\
170 child_pid smallint unsigned,\
171 cookie varchar(255),\
172 machine_id varchar(25),\
173 request_file varchar(255),\
174 referer varchar(255),\
175 remote_host varchar(50),\
176 remote_logname varchar(50),\
177 remote_user varchar(50),\
178 request_duration smallint unsigned,\
179 request_line varchar(255),\
180 request_method varchar(10),\
181 request_protocol varchar(10),\
182 request_time char(28),\
183 request_uri varchar(255),\
184 request_args varchar(255),\
185 server_port smallint unsigned,\
186 ssl_cipher varchar(25),\
187 ssl_keysize smallint unsigned,\
188 ssl_maxkeysize smallint unsigned,\
189 status smallint unsigned,\
190 time_stamp int unsigned,\
191 virtual_host varchar(255))";
192 break;
193 case LOGSQL_TABLE_COOKIES:
194 case LOGSQL_TABLE_HEADERSIN:
195 case LOGSQL_TABLE_HEADERSOUT:
196 case LOGSQL_TABLE_NOTES:
197 create_suffix =
198 "` (id char(19),\
199 item varchar(80),\
200 val varchar(80))";
201 break;
202 }
203
204 if (tabletype) {
205 type_suffix = apr_pstrcat(r->pool, " TYPE=",
206 tabletype, NULL);
207 }
208 /* Find memory long enough to hold the whole CREATE string + \0 */
209 create_sql = apr_pstrcat(r->pool, create_prefix, table_name, create_suffix,
210 type_suffix, NULL);
211
212 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"create string: %s", create_sql);
213
214 if (PQstatus(conn) != CONNECTION_OK) {
215 return LOGSQL_QUERY_NOLINK;
216 }
217 /* A failed mysql_query() may send a SIGPIPE, so we ignore that signal momentarily. */
218 handler = signal(SIGPIPE, SIG_IGN);
219
220 /* Run the create query */
221 result = PQexec(conn, create_sql);
222 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
223 log_error(APLOG_MARK,APLOG_ERR,0, r->server,"failed to create table: %s",
224 table_name);
225 signal(SIGPIPE, handler);
226 PQclear(result);
227 return LOGSQL_TABLE_FAIL;
228 }
229 signal(SIGPIPE, handler);
230 PQclear(result);
231 return LOGSQL_TABLE_SUCCESS;
232}
233
234static char *supported_drivers[] = {"pgsql",NULL};
235static logsql_dbdriver pgsql_driver = {
236 supported_drivers,
237 log_sql_pgsql_connect, /* open DB connection */
238 log_sql_pgsql_close, /* close DB connection */
239 log_sql_pgsql_escape, /* escape query */
240 log_sql_pgsql_query, /* insert query */
241 log_sql_pgsql_create /* create table */
242};
243
244LOGSQL_REGISTER(pgsql) {
245 log_sql_register_driver(p,&pgsql_driver);
246 LOGSQL_REGISTER_RETURN;
247}
diff --git a/src/mod_log_sql_ssl.c b/src/mod_log_sql_ssl.c
new file mode 100644
index 0000000..b3a4929
--- /dev/null
+++ b/src/mod_log_sql_ssl.c
@@ -0,0 +1,106 @@
1/* $Id:mod_log_sql_ssl.c 180 2008-09-21 15:54:12Z urkle@drip.ws $ */
2
3#if defined(WITH_APACHE20)
4# include "apache20.h"
5#elif defined(WITH_APACHE13)
6# include "apache13.h"
7#else
8# error Unsupported Apache version
9#endif
10
11#ifdef HAVE_CONFIG_H
12/* Undefine these to prevent conflicts between Apache ap_config_auto.h and
13 * my config.h. Only really needed for Apache < 2.0.48, but it can't hurt.
14 */
15#undef PACKAGE_BUGREPORT
16#undef PACKAGE_NAME
17#undef PACKAGE_STRING
18#undef PACKAGE_TARNAME
19#undef PACKAGE_VERSION
20
21#include "autoconfig.h"
22#endif
23
24#include "mod_log_sql.h"
25
26#include "mod_ssl.h"
27
28#if defined(WITH_APACHE20)
29static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *header_ssl_lookup = NULL;
30# define TEST_SSL(r) header_ssl_lookup
31#elif defined(WITH_APACHE13)
32# define TEST_SSL(r) ap_ctx_get(r->connection->client->ctx, "ssl")
33# define header_ssl_lookup ssl_var_lookup
34#endif
35
36static const char *extract_ssl_keysize(request_rec *r, char *a)
37{
38 char *result = NULL;
39 if (TEST_SSL(r) != NULL)
40 {
41 result = header_ssl_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_USEKEYSIZE");
42 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"SSL_KEYSIZE: %s", result);
43 if (result && result[0])
44 return result;
45 else
46 return "0";
47 } else {
48 return "0";
49 }
50}
51
52static const char *extract_ssl_maxkeysize(request_rec *r, char *a)
53{
54 char *result = NULL;
55 if (TEST_SSL(r) != NULL)
56 {
57 result = header_ssl_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_ALGKEYSIZE");
58 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"SSL_ALGKEYSIZE: %s", result);
59 if (result && result[0])
60 return result;
61 else
62 return "0";
63 } else {
64 return "0";
65 }
66}
67
68static const char *extract_ssl_cipher(request_rec *r, char *a)
69{
70 char *result = NULL;
71 if (TEST_SSL(r) != NULL)
72 {
73 result = header_ssl_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER");
74 log_error(APLOG_MARK,APLOG_DEBUG,0, r->server,"SSL_CIPHER: %s", result);
75 if (result && result[0])
76 return result;
77 else
78 return "0";
79 } else {
80 return "-";
81 }
82}
83
84
85LOGSQL_REGISTER(ssl)
86{
87 log_sql_register_function(p, "ssl_keysize", extract_ssl_keysize, LOGSQL_FUNCTION_REQ_FINAL);
88 log_sql_register_function(p, "ssl_maxkeysize", extract_ssl_maxkeysize, LOGSQL_FUNCTION_REQ_FINAL);
89 log_sql_register_function(p, "ssl_cipher", extract_ssl_cipher, LOGSQL_FUNCTION_REQ_FINAL);
90
91 log_sql_register_alias(s,p,'q', "ssl_keysize");
92 log_sql_register_alias(s,p,'Q', "ssl_maxkeysize");
93 log_sql_register_alias(s,p,'z', "ssl_cipher");
94
95 log_sql_register_field(p, "ssl_keysize", "ssl_keysize", NULL,
96 "ssl_keysize", LOGSQL_DATATYPE_VARCHAR, 0);
97 log_sql_register_field(p, "ssl_maxkeysize", "ssl_maxkeysize", NULL,
98 "ssl_maxkeysize", LOGSQL_DATATYPE_VARCHAR, 0);
99 log_sql_register_field(p, "ssl_cipher", "ssl_cipher", NULL,
100 "ssl_cipher", LOGSQL_DATATYPE_VARCHAR, 0);
101
102#if defined(WITH_APACHE20)
103 header_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
104#endif
105 LOGSQL_REGISTER_RETURN;
106}
diff --git a/utility/Makefile.in b/utility/Makefile.in
new file mode 100644
index 0000000..fdc3846
--- /dev/null
+++ b/utility/Makefile.in
@@ -0,0 +1,49 @@
1# @configure_input@
2
3top_srcdir = @top_srcdir@
4srcdir = @abs_srcdir@
5top_builddir = @top_builddir@
6builddir = @abs_builddir@
7
8LIBTOOL=@LIBTOOL@
9CFLAGS = -g3 -Wall -fno-strict-aliasing \
10 @APR_INCLUDES@ @APU_INCLUDES@ @PCRE_CFLAGS@ \
11 -I$(top_srcdir)/include
12CPPFLAGS = @APR_CPPFLAGS@
13LDFLAGS = @APR_LIBTOOL@ @APU_LIBTOOL@ @PCRE_LIBS@
14
15ifeq (@OOO_MAINTAIN@,1)
16CFLAGS += -Werror
17endif
18
19STD_DIST = Makefile.in
20
21DISTFILES = $(STD_DIST) $(EXTRA_DIST) $(SOURCES) $(HEADERS)
22
23SOURCES = shell.c config.c logparse.c ap_pcre.c util.c database.c
24HEADERS = shell.h config.h logparse.h ap_pcre.h util.h database.h
25OBJECTS = $(SOURCES:.c=.o)
26DEPS = $(SOURCES:.c=.d)
27TARGETS = mod_log_sql
28
29all: $(TARGETS)
30
31mod_log_sql: $(OBJECTS) $(HEADERS)
32 $(LIBTOOL) --tag=CC --mode=link $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
33
34%.o: %.c %.d
35 gcc -c $(CFLAGS) $(CPPFLAGS) $< -o $@
36%.d: %.c
37 gcc -MM $(CFLAGS) $(CPPFLAGS) $< -o $@
38
39-include $(DEPS)
40
41clean:
42 $(RM) $(OBJECTS) $(TARGETS) $(DEPS)
43
44local-dist: $(DISTFILES)
45 mkdir -p $(DESTDIR)
46 cp -dp --parents $(DISTFILES) $(DESTDIR)
47
48Makefile: Makefile.in ../config.status
49 cd .. && ./config.status
diff --git a/utility/ap_pcre.c b/utility/ap_pcre.c
new file mode 100644
index 0000000..b2b9767
--- /dev/null
+++ b/utility/ap_pcre.c
@@ -0,0 +1,344 @@
1/*************************************************
2 * Perl-Compatible Regular Expressions *
3 *************************************************/
4
5/*
6 This is a library of functions to support regular expressions whose syntax
7 and semantics are as close as possible to those of the Perl 5 language. See
8 the file Tech.Notes for some information on the internals.
9
10 This module is a wrapper that provides a POSIX API to the underlying PCRE
11 functions.
12
13 Written by: Philip Hazel <ph10@cam.ac.uk>
14
15 Copyright (c) 1997-2004 University of Cambridge
16
17 -----------------------------------------------------------------------------
18 Redistribution and use in source and binary forms, with or without
19 modification, are permitted provided that the following conditions are met:
20
21 * Redistributions of source code must retain the above copyright notice,
22 this list of conditions and the following disclaimer.
23
24 * Redistributions in binary form must reproduce the above copyright
25 notice, this list of conditions and the following disclaimer in the
26 documentation and/or other materials provided with the distribution.
27
28 * Neither the name of the University of Cambridge nor the names of its
29 contributors may be used to endorse or promote products derived from
30 this software without specific prior written permission.
31
32 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
36 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 POSSIBILITY OF SUCH DAMAGE.
43 -----------------------------------------------------------------------------
44 */
45
46#include "apr_lib.h"
47#include "apr_strings.h"
48#include "ap_pcre.h"
49#include "pcre.h"
50
51#define APR_WANT_STRFUNC
52#include "apr_want.h"
53
54#ifndef POSIX_MALLOC_THRESHOLD
55#define POSIX_MALLOC_THRESHOLD (10)
56#endif
57
58/* Table of error strings corresponding to POSIX error codes; must be
59 * kept in synch with include/ap_regex.h's AP_REG_E* definitions. */
60
61static const char *const pstring[] = {
62 "", /* Dummy for value 0 */
63 "internal error", /* AP_REG_ASSERT */
64 "failed to get memory", /* AP_REG_ESPACE */
65 "bad argument", /* AP_REG_INVARG */
66 "match failed" /* AP_REG_NOMATCH */
67};
68
69apr_size_t ap_regerror(int errcode, const ap_regex_t *preg, char *errbuf,
70 apr_size_t errbuf_size)
71{
72 const char *message, *addmessage;
73 apr_size_t length, addlength;
74
75 message
76 = (errcode >= (int)(sizeof(pstring)/sizeof(char *))) ? "unknown error code"
77 : pstring[errcode];
78 length = strlen(message) + 1;
79
80 addmessage = " at offset ";
81 addlength
82 = (preg != NULL && (int)preg->re_erroffset != -1) ? strlen(addmessage)
83 + 6
84 : 0;
85
86 if (errbuf_size > 0) {
87 if (addlength > 0 && errbuf_size >= length + addlength)
88 apr_snprintf(errbuf, sizeof errbuf, "%s%s%-6d", message,
89 addmessage, (int)preg->re_erroffset);
90 else {
91 strncpy(errbuf, message, errbuf_size - 1);
92 errbuf[errbuf_size-1] = 0;
93 }
94 }
95
96 return length + addlength;
97}
98
99/*************************************************
100 * Free store held by a regex *
101 *************************************************/
102
103void ap_regfree(ap_regex_t *preg)
104{
105 (pcre_free)(preg->re_pcre);
106}
107
108/*************************************************
109 * Compile a regular expression *
110 *************************************************/
111
112/*
113 Arguments:
114 preg points to a structure for recording the compiled expression
115 pattern the pattern to compile
116 cflags compilation flags
117
118 Returns: 0 on success
119 various non-zero codes on failure
120 */
121
122int ap_regcomp(ap_regex_t *preg, const char *pattern, int cflags)
123{
124 const char *errorptr;
125 int erroffset;
126 int options = 0;
127
128 if ((cflags & AP_REG_ICASE) != 0)
129 options |= PCRE_CASELESS;
130 if ((cflags & AP_REG_NEWLINE) != 0)
131 options |= PCRE_MULTILINE;
132
133 preg->re_pcre = pcre_compile(pattern, options, &errorptr, &erroffset, NULL);
134 preg->re_erroffset = erroffset;
135
136 if (preg->re_pcre == NULL)
137 return AP_REG_INVARG;
138
139 preg->re_nsub = pcre_info((const pcre *)preg->re_pcre, NULL, NULL);
140 return 0;
141}
142
143/*************************************************
144 * Match a regular expression *
145 *************************************************/
146
147/* Unfortunately, PCRE requires 3 ints of working space for each captured
148 substring, so we have to get and release working store instead of just using
149 the POSIX structures as was done in earlier releases when PCRE needed only 2
150 ints. However, if the number of possible capturing brackets is small, use a
151 block of store on the stack, to reduce the use of malloc/free. The threshold is
152 in a macro that can be changed at configure time. */
153
154int ap_regexec(const ap_regex_t *preg, const char *string, apr_size_t nmatch,
155 ap_regmatch_t pmatch[], int eflags)
156{
157 int rc;
158 int options = 0;
159 int *ovector= NULL;
160 int small_ovector[POSIX_MALLOC_THRESHOLD * 3];
161 int allocated_ovector = 0;
162
163 if ((eflags & AP_REG_NOTBOL) != 0)
164 options |= PCRE_NOTBOL;
165 if ((eflags & AP_REG_NOTEOL) != 0)
166 options |= PCRE_NOTEOL;
167
168 ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */
169
170 if (nmatch > 0) {
171 if (nmatch <= POSIX_MALLOC_THRESHOLD) {
172 ovector = &(small_ovector[0]);
173 } else {
174 ovector = (int *)malloc(sizeof(int) * nmatch * 3);
175 if (ovector == NULL)
176 return AP_REG_ESPACE;
177 allocated_ovector = 1;
178 }
179 }
180
181 rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string,
182 (int)strlen(string), 0, options, ovector, nmatch * 3);
183
184 if (rc == 0)
185 rc = nmatch; /* All captured slots were filled in */
186
187 if (rc >= 0) {
188 apr_size_t i;
189 for (i = 0; i < (apr_size_t)rc; i++) {
190 pmatch[i].rm_so = ovector[i*2];
191 pmatch[i].rm_eo = ovector[i*2+1];
192 }
193 if (allocated_ovector)
194 free(ovector);
195 for (; i < nmatch; i++)
196 pmatch[i].rm_so = pmatch[i].rm_eo = -1;
197 return 0;
198 }
199
200 else {
201 if (allocated_ovector)
202 free(ovector);
203 switch (rc) {
204 case PCRE_ERROR_NOMATCH:
205 return AP_REG_NOMATCH;
206 case PCRE_ERROR_NULL:
207 return AP_REG_INVARG;
208 case PCRE_ERROR_BADOPTION:
209 return AP_REG_INVARG;
210 case PCRE_ERROR_BADMAGIC:
211 return AP_REG_INVARG;
212 case PCRE_ERROR_UNKNOWN_NODE:
213 return AP_REG_ASSERT;
214 case PCRE_ERROR_NOMEMORY:
215 return AP_REG_ESPACE;
216#ifdef PCRE_ERROR_MATCHLIMIT
217 case PCRE_ERROR_MATCHLIMIT: return AP_REG_ESPACE;
218#endif
219#ifdef PCRE_ERROR_BADUTF8
220 case PCRE_ERROR_BADUTF8: return AP_REG_INVARG;
221#endif
222#ifdef PCRE_ERROR_BADUTF8_OFFSET
223 case PCRE_ERROR_BADUTF8_OFFSET: return AP_REG_INVARG;
224#endif
225 default:
226 return AP_REG_ASSERT;
227 }
228 }
229}
230
231/*
232 * Here's a pool-based interface to the POSIX-esque ap_regcomp().
233 * Note that we return ap_regex_t instead of being passed one.
234 * The reason is that if you use an already-used ap_regex_t structure,
235 * the memory that you've already allocated gets forgotten, and
236 * regfree() doesn't clear it. So we don't allow it.
237 */
238
239static apr_status_t regex_cleanup(void *preg)
240{
241 ap_regfree((ap_regex_t *) preg);
242 return APR_SUCCESS;
243}
244
245ap_regex_t *ap_pregcomp(apr_pool_t *p, const char *pattern, int cflags)
246{
247 ap_regex_t *preg = apr_palloc(p, sizeof *preg);
248
249 if (ap_regcomp(preg, pattern, cflags)) {
250 return NULL;
251 }
252
253 apr_pool_cleanup_register(p, (void *) preg, regex_cleanup,
254 apr_pool_cleanup_null);
255
256 return preg;
257}
258
259void ap_pregfree(apr_pool_t *p, ap_regex_t *reg)
260{
261 ap_regfree(reg);
262 apr_pool_cleanup_kill(p, (void *) reg, regex_cleanup);
263}
264
265/* This function substitutes for $0-$9, filling in regular expression
266 * submatches. Pass it the same nmatch and pmatch arguments that you
267 * passed ap_regexec(). pmatch should not be greater than the maximum number
268 * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t.
269 *
270 * input should be the string with the $-expressions, source should be the
271 * string that was matched against.
272 *
273 * It returns the substituted string, or NULL on error.
274 *
275 * Parts of this code are based on Henry Spencer's regsub(), from his
276 * AT&T V8 regexp package.
277 */
278
279char * ap_pregsub(apr_pool_t *p, const char *input, const char *source,
280 size_t nmatch, ap_regmatch_t pmatch[])
281{
282 const char *src = input;
283 char *dest, *dst;
284 char c;
285 size_t no;
286 int len;
287
288 if (!source)
289 return NULL;
290 if (!nmatch)
291 return apr_pstrdup(p, src);
292
293 /* First pass, find the size */
294
295 len = 0;
296
297 while ((c = *src++) != '\0') {
298 if (c == '&')
299 no = 0;
300 else if (c == '$' && apr_isdigit(*src))
301 no = *src++ - '0';
302 else
303 no = 10;
304
305 if (no> 9) { /* Ordinary character. */
306 if (c == '\\' && (*src == '$' || *src == '&'))
307 c = *src++;
308 len++;
309 } else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
310 len += pmatch[no].rm_eo - pmatch[no].rm_so;
311 }
312
313 }
314
315 dest = dst = apr_pcalloc(p, len + 1);
316
317 /* Now actually fill in the string */
318
319 src = input;
320
321 while ((c = *src++) != '\0') {
322 if (c == '&')
323 no = 0;
324 else if (c == '$' && apr_isdigit(*src))
325 no = *src++ - '0';
326 else
327 no = 10;
328
329 if (no> 9) { /* Ordinary character. */
330 if (c == '\\' && (*src == '$' || *src == '&'))
331 c = *src++;
332 *dst++ = c;
333 } else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
334 len = pmatch[no].rm_eo - pmatch[no].rm_so;
335 memcpy(dst, source + pmatch[no].rm_so, len);
336 dst += len;
337 }
338
339 }
340 *dst = '\0';
341
342 return dest;
343}
344/* End of pcreposix.c */
diff --git a/utility/ap_pcre.h b/utility/ap_pcre.h
new file mode 100644
index 0000000..e817dc2
--- /dev/null
+++ b/utility/ap_pcre.h
@@ -0,0 +1,178 @@
1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/* Derived from PCRE's pcreposix.h.
18
19 Copyright (c) 1997-2004 University of Cambridge
20
21-----------------------------------------------------------------------------
22Redistribution and use in source and binary forms, with or without
23modification, are permitted provided that the following conditions are met:
24
25 * Redistributions of source code must retain the above copyright notice,
26 this list of conditions and the following disclaimer.
27
28 * Redistributions in binary form must reproduce the above copyright
29 notice, this list of conditions and the following disclaimer in the
30 documentation and/or other materials provided with the distribution.
31
32 * Neither the name of the University of Cambridge nor the names of its
33 contributors may be used to endorse or promote products derived from
34 this software without specific prior written permission.
35
36THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
37AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
40LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46POSSIBILITY OF SUCH DAMAGE.
47-----------------------------------------------------------------------------
48*/
49
50/**
51 * @file ap_regex.h
52 * @brief Apache Regex defines
53 */
54
55#ifndef AP_REGEX_H
56#define AP_REGEX_H
57
58#include "apr.h"
59
60/* Allow for C++ users */
61
62#ifdef __cplusplus
63extern "C" {
64#endif
65
66/* Options for ap_regexec: */
67
68#define AP_REG_ICASE 0x01 /** use a case-insensitive match */
69#define AP_REG_NEWLINE 0x02 /** don't match newlines against '.' etc */
70#define AP_REG_NOTBOL 0x04 /** ^ will not match against start-of-string */
71#define AP_REG_NOTEOL 0x08 /** $ will not match against end-of-string */
72
73#define AP_REG_EXTENDED (0) /** unused */
74#define AP_REG_NOSUB (0) /** unused */
75
76#define AP_MAX_REG_MATCH 10
77
78/* Error values: */
79enum {
80 AP_REG_ASSERT = 1, /** internal error ? */
81 AP_REG_ESPACE, /** failed to get memory */
82 AP_REG_INVARG, /** invalid argument */
83 AP_REG_NOMATCH /** match failed */
84};
85
86/* The structure representing a compiled regular expression. */
87typedef struct {
88 void *re_pcre;
89 apr_size_t re_nsub;
90 apr_size_t re_erroffset;
91} ap_regex_t;
92
93/* The structure in which a captured offset is returned. */
94typedef struct {
95 int rm_so;
96 int rm_eo;
97} ap_regmatch_t;
98
99/* The functions */
100
101/**
102 * Compile a regular expression.
103 * @param preg Returned compiled regex
104 * @param regex The regular expression string
105 * @param cflags Must be zero (currently).
106 * @return Zero on success or non-zero on error
107 */
108int ap_regcomp(ap_regex_t *preg, const char *regex, int cflags);
109
110/**
111 * Match a NUL-terminated string against a pre-compiled regex.
112 * @param preg The pre-compiled regex
113 * @param string The string to match
114 * @param nmatch Provide information regarding the location of any matches
115 * @param pmatch Provide information regarding the location of any matches
116 * @param eflags Bitwise OR of any of AP_REG_* flags
117 * @return 0 for successful match, #REG_NOMATCH otherwise
118 */
119int ap_regexec(const ap_regex_t *preg, const char *string,
120 apr_size_t nmatch, ap_regmatch_t *pmatch, int eflags);
121
122/**
123 * Return the error code returned by regcomp or regexec into error messages
124 * @param errcode the error code returned by regexec or regcomp
125 * @param preg The precompiled regex
126 * @param errbuf A buffer to store the error in
127 * @param errbuf_size The size of the buffer
128 */
129apr_size_t ap_regerror(int errcode, const ap_regex_t *preg,
130 char *errbuf, apr_size_t errbuf_size);
131
132/** Destroy a pre-compiled regex.
133 * @param preg The pre-compiled regex to free.
134 */
135void ap_regfree(ap_regex_t *preg);
136
137/**
138 * Compile a regular expression to be used later
139 * @param p The pool to allocate from
140 * @param pattern the regular expression to compile
141 * @param cflags The bitwise or of one or more of the following:
142 * @li REG_EXTENDED - Use POSIX extended Regular Expressions
143 * @li REG_ICASE - Ignore case
144 * @li REG_NOSUB - Support for substring addressing of matches
145 * not required
146 * @li REG_NEWLINE - Match-any-character operators don't match new-line
147 * @return The compiled regular expression
148 */
149ap_regex_t * ap_pregcomp(apr_pool_t *p, const char *pattern,
150 int cflags);
151
152/**
153 * Free the memory associated with a compiled regular expression
154 * @param p The pool the regex was allocated from
155 * @param reg The regular expression to free
156 */
157void ap_pregfree(apr_pool_t *p, ap_regex_t *reg);
158
159/**
160 * After performing a successful regex match, you may use this function to
161 * perform a series of string substitutions based on subexpressions that were
162 * matched during the call to ap_regexec
163 * @param p The pool to allocate from
164 * @param input An arbitrary string containing $1 through $9. These are
165 * replaced with the corresponding matched sub-expressions
166 * @param source The string that was originally matched to the regex
167 * @param nmatch the nmatch returned from ap_pregex
168 * @param pmatch the pmatch array returned from ap_pregex
169 */
170char * ap_pregsub(apr_pool_t *p, const char *input, const char *source,
171 size_t nmatch, ap_regmatch_t pmatch[]);
172
173#ifdef __cplusplus
174} /* extern "C" */
175#endif
176
177#endif /* AP_REGEX_T */
178
diff --git a/utility/config.c b/utility/config.c
new file mode 100644
index 0000000..6867476
--- /dev/null
+++ b/utility/config.c
@@ -0,0 +1,534 @@
1#include "apr.h"
2#include "apr_file_info.h"
3#include "apr_file_io.h"
4#include "apr_strings.h"
5#include "apr_hash.h"
6#include "apr_uri.h"
7
8#include "shell.h"
9#include "config.h"
10#include "util.h"
11#include "logparse.h"
12#include "autoconfig.h"
13
14apr_hash_t *g_config_opts;
15
16static apr_status_t config_set_string(config_t *cfg, config_opt_t *opt,
17 int argc, const char **argv)
18{
19 int offset = (int)(long)opt->data;
20 char **data = (char **)((void *)cfg + offset);
21 if (argc != 2)
22 return APR_EINVAL;
23 *data = apr_pstrdup(cfg->pool, argv[1]);
24 return APR_SUCCESS;
25}
26
27static apr_status_t config_set_file(config_t *cfg, config_opt_t *opt,
28 int argc, const char **argv)
29{
30 int offset = (int)(long)opt->data;
31 char **data = (char **)((void *)cfg + offset);
32 if (argc != 2)
33 return APR_EINVAL;
34 apr_filepath_merge(data, NULL, argv[1],
35 APR_FILEPATH_TRUENAME, cfg->pool);
36 return APR_SUCCESS;
37}
38
39static apr_status_t config_set_int(config_t *cfg, config_opt_t *opt, int argc,
40 const char **argv)
41{
42 int offset = (int)(long)opt->data;
43 int *data = (int *)((void *)cfg + offset);
44 if (argc != 2)
45 return APR_EINVAL;
46 *data = apr_atoi64(argv[1]);
47 return APR_SUCCESS;
48}
49
50static apr_status_t config_set_flag(config_t *cfg, config_opt_t *opt, int argc,
51 const char **argv)
52{
53 int offset = (int)(long)opt->data;
54 int *data = (int *)((void *)cfg + offset);
55 if (argc != 2)
56 return APR_EINVAL;
57 *data = CHECK_YESNO(argv[1]);
58 return APR_SUCCESS;
59}
60
61static apr_status_t config_set_loglevel(config_t *cfg, config_opt_t *opt,
62 int argc, const char **argv)
63{
64 if (argc != 2)
65 return APR_EINVAL;
66 if (!strcasecmp(argv[1], "error")) {
67 cfg->loglevel = LOGLEVEL_ERROR;
68 } else if (!strcasecmp(argv[1], "notice")) {
69 cfg->loglevel = LOGLEVEL_NOTICE;
70 } else if (!strcasecmp(argv[1], "debug")) {
71 cfg->loglevel = LOGLEVEL_DEBUG;
72 } else {
73 cfg->loglevel = LOGLEVEL_ERROR;
74 }
75 return APR_SUCCESS;
76}
77
78static apr_status_t config_set_inputfile(config_t *cfg, config_opt_t *opt,
79 int argc, const char **argv)
80{
81 config_filestat_t *newp;
82 if (argc != 2)
83 return APR_EINVAL;
84 newp = (config_filestat_t *)apr_array_push(cfg->input_files);
85 char *temp;
86 apr_filepath_merge(&temp, NULL, argv[1],
87 APR_FILEPATH_TRUENAME, cfg->pool);
88 newp->fname = temp;
89 newp->result = "Not Parsed";
90 return APR_SUCCESS;
91}
92
93static apr_status_t config_set_dummy(config_t *cfg, config_opt_t *opt,
94 int argc, const char **argv)
95{
96 return APR_SUCCESS;
97}
98
99static apr_status_t config_set_logformat(config_t *cfg, config_opt_t *opt,
100 int argc, const char **argv)
101{
102 config_logformat_t *format;
103 config_logformat_field_t *field;
104
105 if (argc != 4)
106 return APR_EINVAL;
107
108 format = apr_hash_get(cfg->log_formats, argv[1], APR_HASH_KEY_STRING);
109 if (!format) {
110 format = apr_palloc(cfg->pool, sizeof(config_logformat_t));
111 format->name = apr_pstrdup(cfg->pool, argv[1]);
112 format->fields = apr_array_make(cfg->pool, 5,
113 sizeof(config_logformat_field_t));
114 apr_hash_set(cfg->log_formats, apr_pstrdup(cfg->pool,argv[1]), APR_HASH_KEY_STRING, format);
115 }
116 field = (config_logformat_field_t *)apr_array_push(format->fields);
117 field->name = apr_pstrdup(cfg->pool, argv[2]);
118 field->datatype = apr_pstrdup(cfg->pool, argv[3]);
119 return APR_SUCCESS;
120}
121
122static apr_status_t config_set_output_field(config_t *cfg, config_opt_t *opt,
123 int argc, const char **argv)
124{
125 config_output_field_t *field;
126 char *type, *size, *temp;
127
128 if (argc < 5)
129 return APR_EINVAL;
130 field = (config_output_field_t *)apr_array_push(cfg->output_fields);
131 field->field = apr_pstrdup(cfg->pool, argv[1]);
132 field->source = apr_pstrdup(cfg->pool, argv[4]);
133 field->def = apr_pstrdup(cfg->pool, argv[3]);
134 type = size = apr_pstrdup(cfg->pool, argv[2]);
135 while (*size!='\0' && *size!='(')
136 size++;
137 if (*size == '(') {
138 *size = '\0';
139 size++;
140 temp = size;
141 while (*temp != '\0' && *temp != ')')
142 temp++;
143 *temp = '\0';
144 field->size = apr_atoi64(size);
145 }
146 if (strcasecmp("VARCHAR", type)==0) {
147 field->datatype = LOGSQL_DATATYPE_VARCHAR;
148 } else if (strcasecmp("INT", type)==0) {
149 field->datatype = LOGSQL_DATATYPE_INT;
150 } else if (strcasecmp("CHAR", type)==0) {
151 field->datatype = LOGSQL_DATATYPE_CHAR;
152 } else if (strcasecmp("SMALLINT", type)==0) {
153 field->datatype = LOGSQL_DATATYPE_SMALLINT;
154 } else if (strcasecmp("BIGINT", type)==0) {
155 field->datatype = LOGSQL_DATATYPE_BIGINT;
156 } else {
157 return APR_EINVAL;
158 }
159
160 // Has a function
161 if (argc > 5) {
162 int i;
163 field->fname = apr_pstrdup(cfg->pool, argv[5]);
164 field->func = parser_get_func(field->fname);
165 field->args = apr_pcalloc(cfg->pool, sizeof(char *) * (argc-5+1));
166 for (i=6; i<=argc; i++) {
167 field->args[i-6] = apr_pstrdup(cfg->pool, argv[i]);
168 }
169 }
170
171 return APR_SUCCESS;
172}
173
174static apr_status_t config_set_filter(config_t *cfg, config_opt_t *opt,
175 int argc, const char **argv)
176{
177 int argn = 1;
178 config_filter_t *filter;
179 switch (opt->name[1]) {
180 case 'i': //line
181 filter = apr_array_push(cfg->linefilters);
182 break;
183 case 'r': //pre
184 filter = apr_array_push(cfg->prefilters);
185 break;
186 case 'o': //post
187 filter = apr_array_push(cfg->postfilters);
188 break;
189 }
190
191 if (opt->name[0]=='P') { // Pre or post 2-3 args
192 if (argc == 1)
193 return APR_EINVAL;
194 filter->field = apr_pstrdup(cfg->pool, argv[1]);
195 argn++;
196 } // Otherwise Line based only 1-2 args (no field)
197 if (argc <= argn)
198 return APR_EINVAL;
199 if (*argv[argn] == '+')
200 argn++;
201 if (*argv[argn] == '-') {
202 filter->negative = 1;
203 argn++;
204 }
205 if (filter->negative && argc == argn) {
206 // if no filter for negative.. that's ok.. Assume ALL
207 return APR_SUCCESS;
208 }
209 if (argc <= argn)
210 return APR_EINVAL;
211 filter->filter = apr_pstrdup(cfg->pool, argv[argn]);
212 filter->regex = ap_pregcomp(cfg->pool, filter->filter, AP_REG_EXTENDED|AP_REG_ICASE);
213 return APR_SUCCESS;
214}
215
216void config_dump(config_t *cfg)
217{
218 apr_hash_index_t *hi;
219 int i;
220 config_output_field_t *fields;
221 config_filter_t *filters;
222
223 printf("ErrorLog: %s\n", cfg->errorlog);
224 printf("LogLevel: %d\n", cfg->loglevel);
225
226 printf("BadLineFile: %s\n", cfg->badlinefile);
227 printf("BadLineMax: %d\n", cfg->badlinemax);
228
229 printf("InputDir: %s\n", cfg->input_dir);
230
231 printf("Split input files: %d\n", cfg->split_enabled);
232 printf("Split output directory: %s\n", cfg->split_dir);
233 printf("Split file count: %d\n", cfg->split_count);
234 printf("Split min lines: %'d\n", cfg->split_minimum);
235 printf("Split max lines: %'d\n", cfg->split_maximum);
236
237 printf("DB Driver: %s\n", cfg->dbdriver);
238 printf("DB Params: %s\n", cfg->dbparams);
239
240 printf("Table: %s\n", cfg->table);
241 printf("Transactions: %d\n", cfg->transactions);
242 printf("MachineID: %s\n", cfg->machineid);
243
244 printf("Log formats:\n");
245 for (hi = apr_hash_first(cfg->pool, cfg->log_formats); hi; hi
246 = apr_hash_next(hi)) {
247 config_logformat_t *format;
248 config_logformat_field_t *fields;
249 int i;
250
251 apr_hash_this(hi, NULL, NULL, (void **)&format);
252 printf(">> '%s'\n", format->name);
253 fields = (config_logformat_field_t *)format->fields->elts;
254 for (i=0; i<format->fields->nelts; i++) {
255 printf(">>>> %s:%s\n", fields[i].name, fields[i].datatype);
256 }
257 }
258 printf("Log Format: '%s'\n", cfg->logformat);
259
260 printf("Output Fields:\n");
261 fields = (config_output_field_t *)cfg->output_fields->elts;
262 for (i=0; i<cfg->output_fields->nelts; i++) {
263 printf(">> %s %s(%d) DEFAULT '%s': %s", fields[i].field,
264 logsql_field_datatyeName(fields[i].datatype),
265 fields[i].size, fields[i].def, fields[i].source);
266 if (fields[i].func) {
267 printf(" :: %s(", fields[i].fname);
268 if (fields[i].args) {
269 int a = 0;
270 while (fields[i].args[a]) {
271 printf("%s,", fields[i].args[a]);
272 a++;
273 }
274 }
275 printf(")");
276 }
277 printf("\n");
278 }
279 printf("Filters:\n>> Line:\n");
280 filters = (config_filter_t *)cfg->linefilters->elts;
281 for (i=0; i<cfg->linefilters->nelts; i++) {
282 printf(">>>> %c \"%s\" (%pp)\n",filters[i].negative ? '-':'+',
283 filters[i].filter, filters[i].regex);
284 }
285 printf(">> Pre:\n");
286 filters = (config_filter_t *)cfg->prefilters->elts;
287 for (i=0; i<cfg->prefilters->nelts; i++) {
288 printf(">>>> %s %c \"%s\" (%pp)\n",
289 filters[i].field, filters[i].negative ? '-':'+',
290 filters[i].filter, filters[i].regex);
291 }
292 printf(">> Post:\n");
293 filters = (config_filter_t *)cfg->postfilters->elts;
294 for (i=0; i<cfg->postfilters->nelts; i++) {
295 printf(">>>> %s %c \"%s\" (%pp)\n",
296 filters[i].field, filters[i].negative ? '-':'+',
297 filters[i].filter, filters[i].regex);
298 }
299
300 printf("DryRun: %d\n", cfg->dryrun);
301 printf("Summary: %d\n", cfg->summary);
302}
303
304#define config_get_option(name) apr_hash_get(g_config_opts, name, APR_HASH_KEY_STRING)
305
306static void config_add_option(apr_pool_t *p, const char *const name,
307 const char *const help, config_func_t func, void *data)
308{
309 config_opt_t *opt;
310 if (!g_config_opts) {
311 g_config_opts = apr_hash_make(p);
312 }
313 opt = apr_palloc(p, sizeof(config_opt_t));
314 opt->name = name;
315 opt->help = help;
316 opt->func = func;
317 opt->data = data;
318 apr_hash_set(g_config_opts, lowerstr(p, name), APR_HASH_KEY_STRING, opt);
319}
320
321void config_init(apr_pool_t *p)
322{
323 config_add_option(p, "ErrorLog", "File to log errors", config_set_file,
324 (void *)APR_OFFSETOF(config_t, errorlog));
325 config_add_option(p, "LogLevel", "Set Log Level (error, warn, debug, quiet)",
326 config_set_loglevel, NULL);
327
328 config_add_option(p, "BadLineFile", "File to log bad log lines", config_set_file,
329 (void *)APR_OFFSETOF(config_t, badlinefile));
330 config_add_option(p, "BadLineMax", "Max number of bad lines before aborting",
331 config_set_int, (void *)APR_OFFSETOF(config_t, badlinemax));
332
333
334 config_add_option(p, "InputDirectory", "Directory to scan for log files",
335 config_set_file, (void *)APR_OFFSETOF(config_t, input_dir));
336 config_add_option(p, "InputFile", "Parse only this file",
337 config_set_inputfile, NULL);
338
339 config_add_option(p, "SplitInput",
340 "Split the file into pieces, then process",
341 config_set_flag, (void *)APR_OFFSETOF(config_t, split_enabled));
342 config_add_option(p, "SplitCount",
343 "Split the file into N number of pieces",
344 config_set_int, (void *)APR_OFFSETOF(config_t, split_count));
345 config_add_option(p, "SplitMinLines",
346 "Each split piece will have a minumum of N lines",
347 config_set_int, (void *)APR_OFFSETOF(config_t, split_minimum));
348 config_add_option(p, "SplitMaxLines",
349 "Each split piece will have a maximum of N lines",
350 config_set_int, (void *)APR_OFFSETOF(config_t, split_maximum));
351 config_add_option(p, "SplitDirectory",
352 "Output directory to put intermediate split files",
353 config_set_file, (void *)APR_OFFSETOF(config_t, split_dir));
354
355 config_add_option(p, "ThreadCount",
356 "Numer of threads to use for processing the input files",
357 config_set_int, (void *)APR_OFFSETOF(config_t, thread_count));
358
359 config_add_option(p, "DBDDriver", "DBD Driver to use",
360 config_set_string, (void *)APR_OFFSETOF(config_t, dbdriver));
361 config_add_option(p, "DBDParams", "DBD Connection Parameters",
362 config_set_string, (void *)APR_OFFSETOF(config_t, dbparams));
363 config_add_option(p, "Table", "Table to import the log to",
364 config_set_string, (void *)APR_OFFSETOF(config_t, table));
365 config_add_option(p, "UseTransactions", "Enable Transactions?",
366 config_set_flag, (void *)APR_OFFSETOF(config_t, transactions));
367 config_add_option(p, "MachineID", "Machine ID to set",
368 config_set_string, (void *)APR_OFFSETOF(config_t, machineid));
369
370 config_add_option(p, "LogFormatConfig", "Define input log formats",
371 config_set_logformat, NULL);
372 config_add_option(p, "LogFormat", "Use this logformat when parsing files",
373 config_set_string, (void *)APR_OFFSETOF(config_t, logformat));
374
375 config_add_option(p, "LineFilter",
376 "A regular expression to apply to the input line",
377 config_set_filter, (void *)APR_OFFSETOF(config_t, linefilters));
378 config_add_option(p, "PreFilter",
379 "A regular expression to apply to a specific input field",
380 config_set_filter, (void *)APR_OFFSETOF(config_t, prefilters));
381 config_add_option(p, "PostFilter",
382 "A regular expression to apply to a specific SQL output field",
383 config_set_filter, (void *)APR_OFFSETOF(config_t, postfilters));
384
385 config_add_option(p, "OutputField",
386 "Define output fields: field datatype source optfunc optarg...",
387 config_set_output_field, NULL);
388
389 config_add_option(p, "DryRun", "Don't perform any actual database changes",
390 config_set_flag, (void *)APR_OFFSETOF(config_t, dryrun));
391 config_add_option(p, "Dump", "Dump Configuration and quit",
392 config_set_flag, (void *)APR_OFFSETOF(config_t, dump));
393 config_add_option(p, "Config", "Dummy to handle config directive",
394 config_set_dummy, NULL);
395 config_add_option(p, "Summary", "Show the summary before exit?",
396 config_set_flag, (void *)APR_OFFSETOF(config_t, summary));
397}
398
399config_t *config_create(apr_pool_t *p)
400{
401 config_t *cfg;
402 apr_pool_t *sp;
403 apr_pool_create(&sp, p);
404 cfg = apr_pcalloc(sp, sizeof(config_t));
405 cfg->pool = sp;
406 cfg->loglevel = LOGLEVEL_ERROR;
407 cfg->summary = 1;
408 cfg->transactions = 1;
409 cfg->thread_count = 0; // default zero thread (aka non-threaded)
410 cfg->split_count = 4;
411 cfg->split_minimum = 10000;
412 cfg->split_maximum = 50000;
413 cfg->input_files = apr_array_make(cfg->pool, 2, sizeof(config_filestat_t));
414 cfg->log_formats = apr_hash_make(cfg->pool);
415 cfg->output_fields = apr_array_make(cfg->pool, 10,
416 sizeof(config_output_field_t));
417 cfg->linefilters = apr_array_make(cfg->pool, 2, sizeof(config_filter_t));
418 cfg->prefilters = apr_array_make(cfg->pool, 2, sizeof(config_filter_t));
419 cfg->postfilters = apr_array_make(cfg->pool, 2, sizeof(config_filter_t));
420 return cfg;
421}
422
423apr_status_t config_check(config_t *cfg)
424{
425 apr_status_t ret = APR_SUCCESS;
426 if (!cfg->dbdriver || !cfg->dbparams) {
427 logging_log(cfg, LOGLEVEL_NOISE, "CONFIG: Database configuration is missing");
428 ret = APR_EINVAL;
429 }
430 if (!cfg->table) {
431 logging_log(cfg, LOGLEVEL_NOISE, "CONFIG: No Log Table defined");
432 ret = APR_EINVAL;
433 }
434 if (apr_is_empty_array(cfg->output_fields)) {
435 logging_log(cfg, LOGLEVEL_NOISE, "CONFIG: No Output Fields Defined");
436 ret = APR_EINVAL;
437 }
438 if (apr_hash_count(cfg->log_formats)==0) {
439 logging_log(cfg, LOGLEVEL_NOISE, "CONFIG: No Input Log Formats Defined");
440 ret = APR_EINVAL;
441 }
442#if !defined(HAVE_APR_DBD_TRANSACTION_MODE_GET)
443 if (cfg->transactions) {
444 logging_log(cfg, LOGLEVEL_NOISE, "CONFIG: Disabling Transaction Support. Requires apr-util 1.3.0 or higher");
445 cfg->transactions = 0;
446 }
447#endif
448 return ret;
449}
450
451static int config_merge(void *rec, const char *key, const char *value)
452{
453 config_t *cfg = (config_t *)rec;
454
455 config_opt_t *opt= config_get_option(key);
456 if (opt) {
457 const char *args[] = {
458 key,
459 value };
460 opt->func(cfg, opt, 2, args);
461 } else {
462 logging_log(cfg, LOGLEVEL_NOISE, "Unhandled: %s\n", key);
463 }
464 return 1;
465}
466
467apr_status_t config_read(config_t *cfg, const char *filename,
468 apr_table_t *merge)
469{
470 apr_finfo_t finfo;
471 apr_file_t *file;
472 apr_status_t rv, ret= APR_SUCCESS;
473 apr_pool_t *tp, *targp;
474 config_opt_t *opt;
475 char buff[1024];
476 char *ptr;
477 char **targv;
478 int targc;
479 int line;
480
481 apr_pool_create(&tp, cfg->pool);
482 apr_pool_create(&targp, tp);
483
484 if (apr_stat(&finfo, filename, APR_FINFO_MIN, tp) != APR_SUCCESS) {
485 return APR_ENOENT;
486 }
487 rv = apr_file_open(&file, filename, APR_FOPEN_READ | APR_BUFFERED,
488 APR_OS_DEFAULT, tp);
489 if (rv != APR_SUCCESS)
490 return rv;
491
492 line = 0;
493 do {
494 rv = apr_file_gets(buff, 1024, file);
495 if (rv == APR_SUCCESS) { // we read data
496 line++;
497
498 // skip leading white space
499 for (ptr = buff; *ptr == ' ' || *ptr == '\t'; ptr++)
500 ;
501 line_chomp(ptr);
502
503 // skip comments
504 if (*ptr == '#')
505 continue;
506 if (*ptr == '\0')
507 continue;
508 apr_pool_clear(targp);
509 parser_tokenize_line(ptr, &targv, targp);
510 targc = 0;
511 while (targv[targc])
512 targc++;
513 opt = config_get_option(lowerstr(targp,targv[0]));
514 if (opt) {
515 rv = opt->func(cfg, opt, targc, (const char **)targv);
516 if (APR_STATUS_IS_EINVAL(rv)) {
517 logging_log(cfg, LOGLEVEL_NOISE,
518 "Config Error: Invalid Arguments for %s\n\t%s\n",
519 opt->name, opt->help);
520 ret = rv;
521 }
522 } else {
523 logging_log(cfg, LOGLEVEL_NOISE, "Unhandled: %s\n", targv[0]);
524 }
525 }
526 } while (rv == APR_SUCCESS);
527
528 // Apply merges
529 apr_table_do(config_merge, (void *)cfg, merge, NULL);
530
531 apr_file_close(file);
532 apr_pool_destroy(tp);
533 return ret;
534}
diff --git a/utility/config.h b/utility/config.h
new file mode 100644
index 0000000..a9bd1b5
--- /dev/null
+++ b/utility/config.h
@@ -0,0 +1,190 @@
1#ifndef CONFIG_H_
2#define CONFIG_H_
3
4#include "apr_tables.h"
5#include "apr_hash.h"
6#include "apr_file_io.h"
7#include "ap_pcre.h"
8
9typedef enum {
10 LOGLEVEL_NOISE = 0,
11 LOGLEVEL_NONE,
12 LOGLEVEL_ERROR,
13 LOGLEVEL_NOTICE,
14 LOGLEVEL_DEBUG,
15} loglevel_e;
16
17typedef struct config_dbd_t config_dbd_t;
18typedef struct config_t config_t;
19struct config_t {
20 /** the structures pool (to ease function arguments) */
21 apr_pool_t *pool;
22
23 /** error log file */
24 const char *errorlog;
25 /** log level */
26 loglevel_e loglevel;
27 /** error_log */
28 apr_file_t *errorlog_fp;
29 apr_file_t *errorlog_fperr;
30 apr_pool_t *errorlog_p;
31
32 const char *badlinefile;
33 const char *badlastfile;
34 apr_file_t *badline_fp;
35 int badline_count;
36 int badlinemax;
37
38
39 /** input directory of log files */
40 const char *input_dir;
41 /** list of files to scan */
42 apr_array_header_t *input_files;
43
44 /** split the input file before processing */
45 int split_enabled;
46 /** the number of files to split each input file into */
47 int split_count;
48 /** the minimum number of lines for each piece */
49 int split_minimum;
50 /** the maximum number of lines for each piece */
51 int split_maximum;
52 /** directory to put ouput split files */
53 const char *split_dir;
54
55 /** the number of threads to run the import in */
56 int thread_count;
57
58 /** db connection configuration */
59 const char *dbdriver;
60 const char *dbparams;
61
62 /** Logging table */
63 const char *table;
64 /** Use transactons */
65 int transactions;
66 /** Machine ID */
67 const char *machineid;
68
69 /** Log file formats */
70 apr_hash_t *log_formats;
71 /** format to use to parse files */
72 const char *logformat;
73
74 /** output fields */
75 apr_array_header_t *output_fields;
76
77 /** filter configuration */
78 apr_array_header_t *linefilters;
79 apr_array_header_t *prefilters;
80 apr_array_header_t *postfilters;
81
82 /** Dry Run */
83 int dryrun;
84 /** dump configuration only */
85 int dump;
86
87 /* Show the summary */
88 int summary;
89};
90
91typedef struct config_filestat_t config_filestat_t;
92struct config_filestat_t {
93 const char *fname;
94 apr_size_t linesparsed;
95 apr_size_t lineskipped;
96 apr_size_t linesbad;
97 const char *result;
98 apr_time_t start;
99 apr_time_t stop;
100};
101
102typedef struct config_logformat_t config_logformat_t;
103struct config_logformat_t {
104 const char *name;
105 apr_array_header_t *fields;
106};
107
108typedef struct config_logformat_field_t config_logformat_field_t;
109struct config_logformat_field_t {
110 const char *name;
111 const char *datatype;
112};
113
114typedef struct config_opt_t config_opt_t;
115typedef apr_status_t (*config_func_t)(config_t *cfg, config_opt_t *opt,
116 int argc, const char **argv);
117struct config_opt_t {
118 const char *name;
119 const char *help;
120 config_func_t func;
121 void *data;
122};
123
124typedef struct config_filter_t config_filter_t;
125struct config_filter_t {
126 const char *field;
127 const char *filter;
128 int negative;
129 ap_regex_t *regex;
130};
131
132typedef enum {
133 LOGSQL_DATATYPE_INT = 0,
134 LOGSQL_DATATYPE_SMALLINT,
135 LOGSQL_DATATYPE_VARCHAR,
136 LOGSQL_DATATYPE_CHAR,
137 LOGSQL_DATATYPE_BIGINT
138} logsql_field_datatype;
139#define logsql_field_datatyeName(x) \
140 (x == LOGSQL_DATATYPE_INT ? "INT" \
141 : (x == LOGSQL_DATATYPE_SMALLINT ? "SMALLINT" \
142 : (x == LOGSQL_DATATYPE_VARCHAR ? "VARCHAR" \
143 : (x == LOGSQL_DATATYPE_CHAR ? "CHAR" \
144 : (x == LOGSQL_DATATYPE_BIGINT ? "BIGINT" : "ERR")))))
145
146typedef struct config_output_field_t config_output_field_t;
147
148typedef struct parser_func_t parser_func_t;
149
150struct config_output_field_t {
151 const char *field;
152 logsql_field_datatype datatype;
153 apr_size_t size;
154 const char *def;
155 const char *source;
156 const char *fname;
157 parser_func_t *func;
158 const char **args;
159 void *data;
160};
161
162#define CHECK_YESNO(c) ((!strcasecmp(c,"on") || !strcasecmp(c,"yes")) ? 1 : 0)
163
164/**
165 * Initialize the config parser
166 */
167void config_init(apr_pool_t *p);
168
169/**
170 * Dump the configuration to stdout
171 */
172void config_dump(config_t *cfg);
173
174/**
175 * Checks the configuration to ensure all the required settings are set
176 */
177apr_status_t config_check(config_t *cfg);
178
179/**
180 * Creates the default configuration
181 */
182config_t *config_create(apr_pool_t *p);
183
184/**
185 * Read in a configuration file
186 */
187apr_status_t config_read(config_t *cfg, const char *filename,
188 apr_table_t *merge);
189
190#endif /*CONFIG_H_*/
diff --git a/utility/database.c b/utility/database.c
new file mode 100644
index 0000000..ff81caa
--- /dev/null
+++ b/utility/database.c
@@ -0,0 +1,212 @@
1#include "database.h"
2#include "apu.h"
3#include "apr_dbd.h"
4#include "apr_strings.h"
5
6#include "util.h"
7#include "autoconfig.h"
8
9struct config_dbd_t {
10 const apr_dbd_driver_t *driver;
11 apr_dbd_t *dbd;
12 apr_dbd_prepared_t *stmt;
13 apr_dbd_transaction_t *txn;
14 const char **args;
15};
16
17void database_init(apr_pool_t *p)
18{
19 apr_dbd_init(p);
20}
21
22/** @todo split this into load and connect */
23apr_status_t database_connect(config_t *cfg, config_dbd_t **dbconn)
24{
25 apr_status_t rv;
26 if (!cfg->dbdriver || !cfg->dbparams)
27 return APR_EINVAL;
28 if (!*dbconn) {
29 *dbconn = apr_pcalloc(cfg->pool, sizeof(config_dbd_t));
30 }
31 rv = apr_dbd_get_driver(cfg->pool, cfg->dbdriver, &((*dbconn)->driver));
32 if (rv) {
33
34 logging_log(cfg, LOGLEVEL_ERROR,
35 "DB: Could not load database driver %s. Error %s",
36 cfg->dbdriver, logging_strerror(rv));
37 return rv;
38 }
39
40 rv = apr_dbd_open((*dbconn)->driver, cfg->pool, cfg->dbparams,
41 &((*dbconn)->dbd));
42 if (rv) {
43 logging_log(cfg, LOGLEVEL_ERROR,
44 "DB: Could not connect to database. Error (%d)%s", rv,
45 logging_strerror(rv));
46 return rv;
47 }
48
49 return APR_SUCCESS;
50}
51
52apr_status_t database_disconnect(config_dbd_t *dbconn)
53{
54 return apr_dbd_close(dbconn->driver, dbconn->dbd);
55}
56
57static apr_dbd_prepared_t *database_prepare_insert(config_t *cfg,
58 config_dbd_t *dbconn, apr_pool_t *p)
59{
60 apr_status_t rv;
61 char *sql;
62 int i, f;
63 struct iovec *vec;
64 apr_dbd_prepared_t *stmt= NULL;
65 int nfs = cfg->output_fields->nelts;
66 config_output_field_t *ofields;
67
68 ofields = (config_output_field_t *)cfg->output_fields->elts;
69
70 vec = apr_palloc(p, (nfs*2 + 5) * sizeof(struct iovec));
71 sql = apr_palloc(p, (nfs*3));
72
73 vec[0].iov_base = "INSERT INTO ";
74 vec[0].iov_len = 12;
75 vec[1].iov_base = (void *)cfg->table;
76 vec[1].iov_len = strlen(cfg->table);
77 vec[2].iov_base = " (";
78 vec[2].iov_len = 2;
79 for (i=3, f=0; f<nfs; f++, i+=2) {
80 vec[i].iov_base = (void *)ofields[f].field;
81 vec[i].iov_len = strlen(vec[i].iov_base);
82 vec[i+1].iov_base = ",";
83 vec[i+1].iov_len = 1;
84 memcpy(&sql[f*3], "%s,", 3);
85 }
86 sql[nfs*3-1] = '\0';
87 vec[i-1].iov_base = ") VALUES (";
88 vec[i-1].iov_len = 10;
89 vec[i].iov_base = sql;
90 vec[i].iov_len = nfs*3-1;
91 vec[i+1].iov_base = ")";
92 vec[i+1].iov_len = 1;
93
94 sql = apr_pstrcatv(p, vec, i+2, NULL);
95
96 logging_log(cfg, LOGLEVEL_DEBUG, "DB: Generated SQL: %s", sql);
97
98 rv = apr_dbd_prepare(dbconn->driver, cfg->pool, dbconn->dbd, sql,
99 "INSERT", &stmt);
100
101 if (rv) {
102 logging_log(cfg, LOGLEVEL_NOISE,
103 "DB: Unable to Prepare SQL insert: %s", apr_dbd_error(
104 dbconn->driver, dbconn->dbd, rv));
105 return NULL;
106 }
107 return stmt;
108}
109
110apr_status_t database_insert(config_t *cfg, config_dbd_t *dbconn,
111 apr_pool_t *p, apr_table_t *data)
112{
113 apr_status_t rv;
114 int f, nfs;
115 config_output_field_t *ofields;
116 ofields = (config_output_field_t *)cfg->output_fields->elts;
117 nfs = cfg->output_fields->nelts;
118 // Prepare statement
119 if (!dbconn->stmt) {
120 dbconn->stmt = database_prepare_insert(cfg, dbconn, p);
121 if (!dbconn->stmt) {
122 return APR_EINVAL;
123 }
124 dbconn->args = apr_palloc(cfg->pool, nfs * sizeof(char *));
125 }
126 for (f=0; f<nfs; f++) {
127 dbconn->args[f] = apr_table_get(data, ofields[f].field);
128 }
129 rv = apr_dbd_pquery(dbconn->driver, p, dbconn->dbd, &f,
130 dbconn->stmt, nfs, dbconn->args);
131 if (rv) {
132 logging_log(cfg, LOGLEVEL_ERROR, "DB: Unable to Insert SQL: %s",
133 apr_dbd_error(dbconn->driver, dbconn->dbd, rv));
134 return rv;
135 }
136 return APR_SUCCESS;
137}
138
139apr_status_t database_trans_start(config_t *cfg, config_dbd_t *dbconn,
140 apr_pool_t *p)
141{
142#if HAVE_APR_DBD_TRANSACTION_MODE_GET
143 apr_status_t rv;
144 if (!cfg->transactions)
145 return APR_SUCCESS;
146 if (dbconn->txn) {
147 logging_log(cfg, LOGLEVEL_NOISE,
148 "Transaction Already Started. Something is BROKE");
149 return APR_EINVAL;
150 }
151 logging_log(cfg, LOGLEVEL_DEBUG, "DB: Starting Transaction");
152 rv = apr_dbd_transaction_start(dbconn->driver, p, dbconn->dbd,
153 &dbconn->txn);
154 if (rv)
155 logging_log(cfg, LOGLEVEL_NOISE,
156 "DB: Error Starting Transaction: (%d)%s", rv, apr_dbd_error(
157 dbconn->driver, dbconn->dbd, rv));
158 return rv;
159#else
160 return APR_SUCCESS;
161#endif
162}
163
164apr_status_t database_trans_stop(config_t *cfg, config_dbd_t *dbconn,
165 apr_pool_t *p)
166{
167#if HAVE_APR_DBD_TRANSACTION_MODE_GET
168 apr_status_t rv;
169 if (!cfg->transactions)
170 return APR_SUCCESS;
171 if (!dbconn->txn) {
172 logging_log(cfg, LOGLEVEL_NOISE,
173 "No Transaction Started. Something is BROKE");
174 return APR_EINVAL;
175 }
176 logging_log(cfg, LOGLEVEL_DEBUG, "DB: Stopping Transaction");
177 rv = apr_dbd_transaction_end(dbconn->driver, p, dbconn->txn);
178 if (rv)
179 logging_log(cfg, LOGLEVEL_NOISE,
180 "DB: Error Stopping Transaction: (%d)%s", rv, apr_dbd_error(
181 dbconn->driver, dbconn->dbd, rv));
182
183 dbconn->txn = NULL;
184 return rv;
185#else
186 return APR_SUCCESS;
187#endif
188}
189
190apr_status_t database_trans_abort(config_t *cfg, config_dbd_t *dbconn)
191{
192#if HAVE_APR_DBD_TRANSACTION_MODE_GET
193 apr_status_t rv;
194 if (!cfg->transactions)
195 return APR_SUCCESS;
196 if (!dbconn->txn) {
197 logging_log(cfg, LOGLEVEL_NOISE,
198 "No Transaction Started. Something is BROKE");
199 return APR_EINVAL;
200 }
201 logging_log(cfg, LOGLEVEL_NOTICE, "DB: Aborting Transaction");
202 rv = apr_dbd_transaction_mode_set(dbconn->driver, dbconn->txn,
203 APR_DBD_TRANSACTION_ROLLBACK);
204 if (rv)
205 logging_log(cfg, LOGLEVEL_NOISE,
206 "DB: Error Aborting Transaction: (%d)%s", rv, apr_dbd_error(
207 dbconn->driver, dbconn->dbd, rv));
208 return rv;
209#else
210 return APR_SUCCESS;
211#endif
212}
diff --git a/utility/database.h b/utility/database.h
new file mode 100644
index 0000000..9797d51
--- /dev/null
+++ b/utility/database.h
@@ -0,0 +1,25 @@
1#ifndef DATABASE_H_
2#define DATABASE_H_
3
4#include "apr_pools.h"
5
6#include "config.h"
7
8void database_init(apr_pool_t *p);
9
10apr_status_t database_connect(config_t *cfg, config_dbd_t **dbconn);
11
12apr_status_t database_disconnect(config_dbd_t *dbconn);
13
14apr_status_t database_insert(config_t *cfg, config_dbd_t *dbconn,
15 apr_pool_t *p, apr_table_t *data);
16
17apr_status_t database_trans_start(config_t *cfg, config_dbd_t *dbconn,
18 apr_pool_t *p);
19
20apr_status_t database_trans_stop(config_t *cfg, config_dbd_t *dbconn,
21 apr_pool_t *p);
22
23apr_status_t database_trans_abort(config_t *cfg, config_dbd_t *dbconn);
24
25#endif /*DATABASE_H_*/
diff --git a/utility/logparse.c b/utility/logparse.c
new file mode 100644
index 0000000..1b3cc97
--- /dev/null
+++ b/utility/logparse.c
@@ -0,0 +1,768 @@
1#include "logparse.h"
2#include "apr_file_info.h"
3#include "apr_file_io.h"
4#include "apr_strings.h"
5#include "apr_time.h"
6
7#include "time.h"
8#include "stdlib.h"
9
10#include "util.h"
11#include "ap_pcre.h"
12#include "database.h"
13
14apr_hash_t *g_parser_funcs;
15void **g_parser_linedata;
16
17static apr_status_t parser_func_wrap(apr_pool_t *p, config_t *cfg,
18 config_output_field_t *field, const char *value, const char **ret)
19{
20 if (field->args[0] && field->args[1]) {
21 struct iovec vec[3];
22 apr_size_t len;
23
24 vec[0].iov_base = (void *)field->args[0];
25 vec[0].iov_len = strlen(field->args[0]);
26 vec[1].iov_base = (void *)value;
27 vec[1].iov_len = strlen(value);
28 vec[2].iov_base = (void *)field->args[1];
29 vec[2].iov_len = strlen(field->args[1]);
30
31 *ret = apr_pstrcatv(p, vec, 3, &len);
32 } else {
33 logging_log(cfg, LOGLEVEL_NOISE, "wrap requires before and after strings");
34 return APR_EINVAL;
35 }
36 return APR_SUCCESS;
37}
38
39static apr_status_t parser_func_regexmatch(apr_pool_t *p, config_t *cfg,
40 config_output_field_t *field, const char *value, const char **ret)
41{
42 struct {
43 ap_regex_t *rx;
44 const char *substr;
45 }*_data;
46 ap_regmatch_t regm[AP_MAX_REG_MATCH];
47 // Check if a regular expression configured
48 if (!field->args[0])
49 return APR_EINVAL;
50 if (!field->data) {
51 // pre compile the regex
52 _data = apr_palloc(cfg->pool, sizeof(ap_regex_t)+sizeof(const char *));
53 _data->rx = ap_pregcomp(cfg->pool, field->args[0],
54 AP_REG_EXTENDED|AP_REG_ICASE);
55 if (field->args[1]) {
56 _data->substr = field->args[1];
57 } else {
58 _data->substr = "$1";
59 }
60 if (!_data->rx) {
61 logging_log(cfg, LOGLEVEL_NOISE, "Failed to compile regular expression");
62 return APR_EINVAL;
63 }
64 field->data = _data;
65 } else
66 _data = field->data;
67
68 if (!ap_regexec(_data->rx, value, AP_MAX_REG_MATCH, regm, 0)) {
69 *ret = ap_pregsub(p, _data->substr, value, AP_MAX_REG_MATCH, regm);
70 }
71 logging_log(cfg, LOGLEVEL_DEBUG, "REGEX: matched %s against %s to %s", value,
72 field->args[0], *ret);
73 return APR_SUCCESS;
74}
75
76static apr_status_t parser_func_totimestamp(apr_pool_t *p, config_t *cfg,
77 config_output_field_t *field, const char *value, const char **ret)
78{
79 time_t time;
80 struct tm ts;
81
82 //memset(&ts,0,sizeof(struct tm));
83
84 strptime(value, "%d/%b/%Y:%H:%M:%S %z", &ts);
85 time = mktime(&ts);
86
87 *ret = apr_itoa(p, time);
88 return APR_SUCCESS;
89}
90
91static apr_status_t parser_func_machineid(apr_pool_t *p, config_t *cfg,
92 config_output_field_t *field, const char *value, const char **ret)
93{
94 if (cfg->machineid) {
95 *ret = apr_pstrdup(p, cfg->machineid);
96 }
97 return APR_SUCCESS;
98}
99
100static apr_status_t parser_func_queryarg(apr_pool_t *p, config_t *cfg,
101 config_output_field_t *field, const char *value, const char **ret)
102{
103 apr_table_t *query = parser_get_linedata(field->func);
104
105 if (!field->args[0]) {
106 logging_log(cfg, LOGLEVEL_NOISE, "queryarg requires name of query arg");
107 return APR_EINVAL;
108 }
109
110 if (!query) {
111 char *query_beg;
112
113 query = apr_table_make(p,3);
114
115 query_beg = strchr(value, '?');
116 // if we have a query string, rip it apart
117 if (query_beg) {
118 char *key;
119 char *value;
120 const char *delim = "&";
121 char *query_string;
122 char *strtok_state;
123 char *query_end = strrchr(++query_beg,' ');
124
125 query_string = apr_pstrndup(p, query_beg, query_end-query_beg);
126 logging_log(cfg, LOGLEVEL_DEBUG, "QUERY: Found String %pp, %pp, %s",
127 query_beg, query_end, query_string);
128 if (field->args[1]) {
129 delim = field->args[1];
130 }
131 key = apr_strtok(query_string, delim, &strtok_state);
132 while (key) {
133 value = strchr(key, '=');
134 if (value) {
135 *value = '\0'; /* Split the string in two */
136 value++; /* Skip past the = */
137 }
138 else {
139 value = "1";
140 }
141 ap_unescape_url(key);
142 ap_unescape_url(value);
143 apr_table_set(query, key, value);
144
145 logging_log(cfg, LOGLEVEL_DEBUG,
146 "QUERY: Found arg: %s = %s", key, value);
147
148 key = apr_strtok(NULL, delim, &strtok_state);
149 }
150 }
151 parser_set_linedata(field->func,query);
152 }
153 *ret = apr_table_get(query, field->args[0]);
154 return APR_SUCCESS;
155}
156
157parser_func_t *parser_get_func(const char *name)
158{
159 return apr_hash_get(g_parser_funcs, name, APR_HASH_KEY_STRING);
160}
161
162static void parser_add_func(apr_pool_t *p, const char *const name,
163 parser_func_f func, int id)
164{
165 parser_func_t *s;
166 if (!g_parser_funcs) {
167 g_parser_funcs = apr_hash_make(p);
168 }
169 s = apr_palloc(p, sizeof(parser_func_t));
170 s->func = func;
171 s->pos = id;
172 s->data = NULL;
173 s->linedata = &g_parser_linedata;
174 apr_hash_set(g_parser_funcs, lowerstr(p, name), APR_HASH_KEY_STRING, s);
175}
176
177void parser_init(apr_pool_t *p)
178{
179 int i = 0;
180 parser_add_func(p, "regexmatch", parser_func_regexmatch, ++i);
181 parser_add_func(p, "totimestamp", parser_func_totimestamp, ++i);
182 parser_add_func(p, "machineid", parser_func_machineid, ++i);
183 parser_add_func(p, "queryarg", parser_func_queryarg, ++i);
184 parser_add_func(p, "wrap", parser_func_wrap, ++i);
185 g_parser_linedata = apr_pcalloc(p, sizeof(void *) * (i+1));
186 g_parser_linedata[0] = (void *)i;
187}
188
189void parser_find_logs(config_t *cfg)
190{
191 apr_pool_t *tp;
192 apr_dir_t *dir;
193 apr_finfo_t finfo;
194 config_filestat_t *newp;
195
196 logging_log(cfg, LOGLEVEL_NOTICE, "Find Log files");
197 if (!cfg->input_dir)
198 return;
199 apr_pool_create(&tp, cfg->pool);
200 if (apr_dir_open(&dir, cfg->input_dir, tp)==APR_SUCCESS) {
201 while (apr_dir_read(&finfo, APR_FINFO_NAME | APR_FINFO_TYPE, dir)
202 == APR_SUCCESS) {
203 char *temp;
204 if (finfo.filetype == APR_DIR)
205 continue;
206 newp = (config_filestat_t *)apr_array_push(cfg->input_files);
207 newp->result = "Not Parsed";
208 apr_filepath_merge(&temp, cfg->input_dir, finfo.name,
209 APR_FILEPATH_TRUENAME, cfg->pool);
210 newp->fname = temp;
211 }
212 apr_dir_close(dir);
213 }
214 apr_pool_destroy(tp);
215}
216
217#define BUFFER_SIZE (16 * 1024)
218
219void parser_split_logs(config_t *cfg)
220{
221 apr_pool_t *tp, *tfp;
222 apr_array_header_t *foundfiles;
223 config_filestat_t *filelist;
224 config_filestat_t *newfile;
225 apr_file_t *infile;
226 int f, l;
227 apr_status_t rv;
228 apr_finfo_t finfo;
229 char buff[BUFFER_SIZE];
230 int linecount;
231 int piecesize;
232
233 if (!cfg->split_enabled) return;
234 if (!cfg->split_dir) {
235 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Missing Split Output directory");
236 return;
237 }
238 apr_pool_create(&tp, cfg->pool);
239 apr_pool_create(&tfp, tp);
240
241 if (APR_SUCCESS != apr_stat(&finfo, cfg->split_dir, APR_FINFO_MIN, tp)) {
242 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Directory %s does not exist", cfg->split_dir);
243 return;
244 }
245 foundfiles = apr_array_copy(tp, cfg->input_files);
246 apr_array_clear(cfg->input_files);
247
248 filelist = (config_filestat_t *)foundfiles->elts;
249 for (f=0, l=foundfiles->nelts; f < l; f++) {
250 apr_pool_clear(tfp);
251 logging_log(cfg, LOGLEVEL_NOTICE, "SPLITTER: Begin Splitting Log File '%s'", filelist[f].fname);
252 rv = apr_file_open(&infile, filelist[f].fname, APR_FOPEN_READ, APR_OS_DEFAULT, tfp);
253
254 if (rv != APR_SUCCESS) {
255 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Could not open %s", filelist[f].fname);
256 return;
257 }
258 linecount = 0;
259 while (apr_file_eof(infile) == APR_SUCCESS) {
260 apr_size_t read = BUFFER_SIZE;
261 char *p;
262 apr_file_read(infile, buff, &read);
263 p = buff;
264 while ((p = memchr(p, '\n', (buff + read) - p))) {
265 ++p;
266 ++linecount;
267 }
268 }
269 // now we know how long it is. Lets split up the file
270 piecesize = linecount / cfg->split_count;
271 if (piecesize < cfg->split_minimum)
272 piecesize = cfg->split_minimum;
273 if (piecesize > cfg->split_maximum && cfg->split_maximum > 0)
274 piecesize = cfg->split_maximum;
275 if (piecesize > linecount) {
276 // File is smaller than piece size just add it back in as is
277 newfile = (config_filestat_t *)apr_array_push(cfg->input_files);
278 newfile->result = "Not Parsed";
279 newfile->fname = filelist[f].fname;
280 } else {
281 //split apart the files
282 int cur_line = 0;
283 int file_count = 1;
284 int out_lines = 0;
285 const char *basefile, *file;
286 apr_file_t *outfile;
287 char trail[2048];
288 apr_size_t trail_size = 0;
289 apr_size_t write;
290 apr_off_t off = 0;
291
292 apr_file_seek(infile, APR_SET, &off);
293
294 basefile = apr_pstrdup(tfp, basename(apr_pstrdup(tfp, filelist[f].fname)));
295
296 file = apr_psprintf(tfp, "%s/%s-%d", cfg->split_dir, basefile, file_count++);
297 logging_log(cfg, LOGLEVEL_NOTICE, "SPLITTER: Creating output file %s", file);
298 rv = apr_file_open(&outfile, file, APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, APR_OS_DEFAULT, tfp);
299 if (rv != APR_SUCCESS) {
300 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Could not open %s (%d)", file, rv);
301 return;
302 }
303 newfile = (config_filestat_t *)apr_array_push(cfg->input_files);
304 newfile->result = "Not Parsed";
305 newfile->fname = apr_pstrdup(cfg->pool, file);
306
307 while (apr_file_eof(infile) == APR_SUCCESS) {
308 apr_size_t read = BUFFER_SIZE;
309 char *p, *pp, *buff_start;
310 apr_file_read(infile, buff, &read);
311 buff_start = p = pp = buff;
312 if (trail_size) {
313 p = memchr(p, '\n', (buff + read) - p);
314 if (p) {
315 //printf("Trail Line: %p, %p, %d\n", pp, p, (p - pp) + trail_size);
316 ++p;
317 pp = p;
318 ++cur_line;
319 ++out_lines;
320 // write out to file
321 apr_file_write(outfile, trail, &trail_size);
322 trail_size = 0;
323 } else {
324 if ((read + trail_size) > 2048) {
325 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Excessively long line %d in file %s", cur_line, filelist[f].fname);
326 exit(1);
327 } else {
328 memcpy(trail+trail_size, buff, read);
329 trail_size += read;
330 }
331 }
332 }
333 while ((p = memchr(p, '\n', (buff + read) - p))) {
334 //printf("Line: %p, %p, %d\n", pp, p, (p - pp));
335 if (out_lines == piecesize) {
336 // Write out to file
337 write = pp - buff_start;
338 apr_file_write(outfile, buff_start, &write);
339 buff_start = pp;
340 out_lines = 0;
341 // Open new file
342 file = apr_psprintf(tfp, "%s/%s-%d", cfg->split_dir, basefile, file_count++);
343 logging_log(cfg, LOGLEVEL_NOTICE, "SPLITTER: Creating output file %s", file);
344 rv = apr_file_open(&outfile, file, APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, APR_OS_DEFAULT, tfp);
345 if (rv != APR_SUCCESS) {
346 logging_log(cfg, LOGLEVEL_NOISE, "SPLITTER: Could not open %s (%d)", file, rv);
347 return;
348 }
349 newfile = (config_filestat_t *)apr_array_push(cfg->input_files);
350 newfile->result = "Not Parsed";
351 newfile->fname = apr_pstrdup(cfg->pool, file);
352 }
353 ++p;
354 pp = p;
355 ++cur_line;
356 ++out_lines;
357 }
358 // Write out to file
359 write = pp - buff_start;
360 apr_file_write(outfile, buff_start, &write);
361
362 trail_size = (buff+read) - pp;
363 if (trail_size) {
364 memcpy(trail, pp, trail_size);
365 }
366 }
367 }
368 }
369 apr_pool_destroy(tfp);
370 apr_pool_destroy(tp);
371}
372
373apr_status_t parser_logbadline(config_t *cfg, const char *filename,
374 const char *badline)
375{
376 apr_status_t rv = APR_SUCCESS;
377 apr_size_t len;
378 struct iovec vec[5];
379
380 if (cfg->badlinefile) {
381 if (!cfg->badline_fp) {
382 rv = apr_file_open(&cfg->badline_fp, cfg->badlinefile,
383 APR_FOPEN_CREATE | APR_FOPEN_WRITE | APR_FOPEN_APPEND,
384 APR_OS_DEFAULT, cfg->pool);
385 if (rv) {
386 logging_log(cfg, LOGLEVEL_NOISE,
387 "Error opening badline file %s\n", cfg->badlinefile);
388 cfg->badlinefile = NULL;
389 }
390 }
391 if (!rv) {
392 if (filename != cfg->badlastfile){
393 char date[APR_RFC822_DATE_LEN];
394 vec[0].iov_base = "Starting BadLines for \"";
395 vec[0].iov_len = sizeof("Starting BadLines for \"")-1;
396 vec[1].iov_base = (void *)filename;
397 vec[1].iov_len = strlen(filename);
398 vec[2].iov_base = "\" on ";
399 vec[2].iov_len = sizeof("\" on ")-1;
400 apr_rfc822_date(date, apr_time_now());
401 vec[3].iov_base = date;
402 vec[3].iov_len = APR_RFC822_DATE_LEN-1;
403 vec[4].iov_base = "\n";
404 vec[4].iov_len = 1;
405 apr_file_writev(cfg->badline_fp, vec,5, &len);
406 cfg->badlastfile = filename;
407 }
408
409 if ((++cfg->badline_count) > cfg->badlinemax) {
410 logging_log(cfg, LOGLEVEL_NOISE,
411 "Found more than %d bad lines (found %d)",
412 cfg->badlinemax, cfg->badline_count);
413 rv = APR_EINVAL;
414 } else {
415 vec[0].iov_base = (void *)badline;
416 vec[0].iov_len = strlen(badline);
417 vec[1].iov_base = "\n";
418 vec[1].iov_len = 1;
419 apr_file_writev(cfg->badline_fp, vec,2, &len);
420 }
421 }
422 }
423 return rv;
424}
425
426/*
427 * Modified version of apr_tokenize_to_argv to add [] as quoting characters
428 *
429 * token_context: Context from which pool allocations will occur.
430 * arg_str: Input string for conversion to argv[].
431 * argv_out: Output location. This is a pointer to an array
432 * of pointers to strings (ie. &(char *argv[]).
433 * This value will be allocated from the contexts
434 * pool and filled in with copies of the tokens
435 * found during parsing of the arg_str.
436 */
437apr_status_t parser_tokenize_line(const char *arg_str, char ***argv_out,
438 apr_pool_t *token_context)
439{
440 const char *cp;
441 const char *ct;
442 char *cleaned, *dirty;
443 int escaped;
444 int isquoted, numargs = 0, argnum;
445
446#define SKIP_WHITESPACE(cp) \
447 for ( ; *cp == ' ' || *cp == '\t'; ) { \
448 cp++; \
449 };
450
451#define CHECK_QUOTATION(cp,isquoted) \
452 isquoted = 0; \
453 if (*cp == '"') { \
454 isquoted = 1; \
455 cp++; \
456 } \
457 else if (*cp == '\'') { \
458 isquoted = 2; \
459 cp++; \
460 } \
461 else if (*cp == '[') { \
462 isquoted = 3; \
463 cp++; \
464 }
465
466 /* DETERMINE_NEXTSTRING:
467 * At exit, cp will point to one of the following: NULL, SPACE, TAB or QUOTE.
468 * NULL implies the argument string has been fully traversed.
469 */
470#define DETERMINE_NEXTSTRING(cp,isquoted) \
471 for ( ; *cp != '\0'; cp++) { \
472 if ( (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t' || \
473 *(cp+1) == '"' || *(cp+1) == '\'' || \
474 *(cp+1) == '[' || *(cp+1) == ']'))) { \
475 cp++; \
476 continue; \
477 } \
478 if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \
479 || (isquoted == 1 && *cp == '"') \
480 || (isquoted == 2 && *cp == '\'') \
481 || (isquoted == 3 && *cp == ']') \
482 ) { \
483 break; \
484 } \
485 }
486
487 /* REMOVE_ESCAPE_CHARS:
488 * Compresses the arg string to remove all of the '\' escape chars.
489 * The final argv strings should not have any extra escape chars in it.
490 */
491#define REMOVE_ESCAPE_CHARS(cleaned, dirty, escaped) \
492 escaped = 0; \
493 while(*dirty) { \
494 if (!escaped && *dirty == '\\') { \
495 escaped = 1; \
496 } \
497 else { \
498 escaped = 0; \
499 *cleaned++ = *dirty; \
500 } \
501 ++dirty; \
502 } \
503 *cleaned = 0; /* last line of macro... */
504
505 cp = arg_str;
506 SKIP_WHITESPACE(cp);
507 ct = cp;
508
509 /* This is ugly and expensive, but if anyone wants to figure a
510 * way to support any number of args without counting and
511 * allocating, please go ahead and change the code.
512 *
513 * Must account for the trailing NULL arg.
514 */
515 numargs = 1;
516 while (*ct != '\0') {
517 CHECK_QUOTATION(ct, isquoted)
518 ;
519 DETERMINE_NEXTSTRING(ct, isquoted);
520 if (*ct != '\0') {
521 ct++;
522 }
523 numargs++;
524 SKIP_WHITESPACE(ct);
525 }
526 *argv_out = apr_palloc(token_context, numargs * sizeof(char*));
527
528 /* determine first argument */
529 for (argnum = 0; argnum < (numargs-1); argnum++) {
530 SKIP_WHITESPACE(cp);
531 CHECK_QUOTATION(cp, isquoted)
532 ;
533 ct = cp;
534 DETERMINE_NEXTSTRING(cp, isquoted);
535 cp++;
536 (*argv_out)[argnum] = apr_palloc(token_context, cp - ct);
537 apr_cpystrn((*argv_out)[argnum], ct, cp - ct);
538 cleaned = dirty = (*argv_out)[argnum];
539 REMOVE_ESCAPE_CHARS(cleaned, dirty, escaped)
540 ;
541 }
542 (*argv_out)[argnum] = NULL;
543
544 return APR_SUCCESS;
545}
546
547apr_status_t parser_parsefile(config_t *cfg, config_dbd_t *dbconn,
548 config_filestat_t *fstat)
549{
550 apr_pool_t *tp, *targp;
551 apr_file_t *file;
552 apr_status_t rv;
553 char buff[2048];
554 char readbuff[BUFFER_SIZE];
555 char **targv;
556 int targc;
557
558 apr_pool_create(&tp, cfg->pool);
559 apr_pool_create(&targp, tp);
560
561 logging_log(cfg, LOGLEVEL_NOTICE, "PARSER: Begin Parsing Log File '%s'", fstat->fname);
562
563 rv = apr_file_open(&file, fstat->fname, APR_FOPEN_READ, APR_OS_DEFAULT, tp);
564 apr_file_buffer_set(file, readbuff, BUFFER_SIZE);
565 if (rv != APR_SUCCESS) {
566 logging_log(cfg, LOGLEVEL_NOISE, "PARSER: Could not open %s", fstat->fname);
567 return rv;
568 }
569
570 fstat->linesparsed = 0;
571 // Start Transaction
572 fstat->start = apr_time_now();
573 if (!cfg->dryrun && database_trans_start(cfg, dbconn, tp)) {
574 fstat->result = "Database Transaction Error";
575 fstat->stop = apr_time_now();
576 return rv;
577 }
578
579 do {
580 rv = apr_file_gets(buff, 2048, file);
581 if (rv == APR_SUCCESS) {
582 int i,m, cont = 0;
583 config_filter_t *filters;
584
585 fstat->linesparsed++;
586 // chomp off newline
587 line_chomp(buff);
588 // Run line filters
589 for (i=0, m=cfg->linefilters->nelts,
590 filters = (config_filter_t *)cfg->linefilters->elts;
591 i<m; i++) {
592 if (!filters[i].regex || ap_regexec(filters[i].regex, buff, 0, NULL,0)==0) {
593 if (filters[i].negative) {
594 logging_log(cfg, LOGLEVEL_DEBUG,
595 "PARSER: LINEFILTER: Skipping Line %d due to Filter (%d)%s",
596 fstat->linesparsed, i, filters[i].filter);
597 fstat->lineskipped++;
598 cont = 1;
599 } else {
600 logging_log(cfg, LOGLEVEL_DEBUG,
601 "PARSER: LINEFILTER: Force Parsing Line %d due to Filter (%d)%s",
602 fstat->linesparsed, i, filters[i].filter);
603 }
604 break;
605 }
606 }
607 if (cont) continue;
608
609 apr_pool_clear(targp);
610 parser_tokenize_line(buff, &targv, targp);
611 targc = 0;
612 while (targv[targc])
613 targc++;
614 rv = parser_processline(targp, cfg, dbconn, fstat, targv, targc);
615 if (rv != APR_SUCCESS) {
616 int i;
617
618 fstat->linesbad++;
619 rv = parser_logbadline(cfg, fstat->fname, buff);
620 if (rv) {
621 if (!cfg->dryrun) database_trans_abort(cfg, dbconn);
622 logging_log(cfg, LOGLEVEL_ERROR, "Line %d(%d): %s", fstat->linesparsed,
623 targc, buff);
624 for (i = 0; targv[i]; i++) {
625 logging_log(cfg, LOGLEVEL_ERROR, "Arg (%d): '%s'", i,
626 targv[i]);
627 }
628 }
629 }
630 } else {
631 rv = APR_SUCCESS;
632 break;
633 }
634 } while (rv == APR_SUCCESS);
635 apr_file_close(file);
636 // Finish Transaction
637 if (!cfg->dryrun && database_trans_stop(cfg, dbconn, tp)) {
638 fstat->result = apr_psprintf(cfg->pool,
639 "Input line %d, Database Transaction Error",
640 fstat->linesparsed);
641 }
642
643 apr_pool_destroy(tp);
644 logging_log(cfg, LOGLEVEL_NOTICE,
645 "PARSER: Finish Parsing Log File '%s'. Lines: (%d/%d)",
646 fstat->fname, fstat->linesparsed - fstat->lineskipped, fstat->linesparsed);
647 if (!rv) {
648 fstat->result = "File Parsed Succesfully";
649 }
650 fstat->stop = apr_time_now();
651 return rv;
652}
653
654apr_status_t parser_processline(apr_pool_t *ptemp, config_t *cfg,
655 config_dbd_t *dbconn, config_filestat_t *fstat, char **argv, int argc)
656{
657 config_logformat_t *fmt;
658 config_logformat_field_t *ifields;
659 config_output_field_t *ofields;
660 config_filter_t *filters;
661 apr_table_t *datain;
662 apr_table_t *dataout;
663 apr_status_t rv= APR_SUCCESS;
664 int i,m;
665
666 fmt = apr_hash_get(cfg->log_formats, cfg->logformat, APR_HASH_KEY_STRING);
667 if (!fmt) {
668 logging_log(cfg, LOGLEVEL_NOISE, "PARSER: No Input Log format");
669 return APR_EINVAL;
670 }
671 if (fmt->fields->nelts != argc) {
672 logging_log(cfg, LOGLEVEL_NOISE,
673 "PARSER: Input line field number differs from expected. Expected %d got %d.",
674 fmt->fields->nelts, argc);
675 fstat->result = apr_psprintf(cfg->pool,
676 "Input line %d is badly formatted (wrong number of fields)",
677 fstat->linesparsed);
678 return APR_EINVAL;
679 }
680
681 datain = apr_table_make(ptemp, fmt->fields->nelts);
682 dataout = apr_table_make(ptemp, cfg->output_fields->nelts);
683
684 ifields = (config_logformat_field_t *)fmt->fields->elts;
685 for (i=0; i<fmt->fields->nelts; i++) {
686 apr_table_setn(datain, ifields[i].name, argv[i]);
687 }
688 // Run Pre Filters
689 for (i=0, m=cfg->prefilters->nelts,
690 filters = (config_filter_t *)cfg->prefilters->elts;
691 i<m; i++) {
692 const char *temp = apr_table_get(datain, filters[i].field);
693 if (temp && (!filters[i].regex || ap_regexec(filters[i].regex, temp, 0, NULL,0)==0)) {
694 if (filters[i].negative) {
695 logging_log(cfg, LOGLEVEL_DEBUG,
696 "PARSER: PREFILTER: Skipping Line %d due to Filter (%d)%s",
697 fstat->linesparsed, i, filters[i].filter);
698 fstat->lineskipped++;
699 return APR_SUCCESS;
700 } else {
701 logging_log(cfg, LOGLEVEL_DEBUG,
702 "PARSER: PREFILTER: Force Parsing Line %d due to Filter (%d)%s",
703 fstat->linesparsed, i, filters[i].filter);
704 }
705 break;
706 }
707 }
708
709 ofields = (config_output_field_t *)cfg->output_fields->elts;
710 // clear out ofield function per-line data
711 memset(&g_parser_linedata[1],0,sizeof(void *)*(int)g_parser_linedata[0]);
712 // Convert input fields to output fields
713 for (i=0,m=cfg->output_fields->nelts; i<m; i++) {
714 const char *val;
715 val = apr_table_get(datain, ofields[i].source);
716 // If we can't find the source field just continue
717 if (!val && !(ofields[i].source[0]=='\0' && ofields[i].func)) {
718 apr_table_setn(dataout, ofields[i].field, ofields[i].def);
719 continue;
720 }
721 if (!ofields[i].func) {
722 apr_table_setn(dataout, ofields[i].field, val);
723 } else {
724 const char *ret= NULL;
725 rv = ((parser_func_t *)ofields[i].func)->func(ptemp, cfg,
726 &ofields[i], val, &ret);
727 if (rv) {
728 fstat->result = apr_psprintf(cfg->pool,
729 "Input line %d, Parser function %s returned error (%d)%s",
730 fstat->linesparsed, ofields[i].fname, rv, logging_strerror(rv));
731 return rv;
732 }
733 apr_table_setn(dataout, ofields[i].field, ret ? ret : ofields[i].def);
734 }
735 }
736
737 // Run Post filters
738 for (i=0, m=cfg->postfilters->nelts,
739 filters = (config_filter_t *)cfg->postfilters->elts;
740 i<m; i++) {
741 const char *temp = apr_table_get(dataout, filters[i].field);
742 if (temp && (!filters[i].regex || ap_regexec(filters[i].regex, temp, 0, NULL,0)==0)) {
743 if (filters[i].negative) {
744 logging_log(cfg, LOGLEVEL_DEBUG,
745 "PARSER: POSTFILTER: Skipping Line %d due to Filter (%d)%s",
746 fstat->linesparsed, i, filters[i].filter);
747 fstat->lineskipped++;
748 return APR_SUCCESS;
749 } else {
750 logging_log(cfg, LOGLEVEL_DEBUG,
751 "PARSER: POSTFILTER: Force Parsing Line %d due to Filter (%d)%s",
752 fstat->linesparsed, i, filters[i].filter);
753 }
754 break;
755 }
756 }
757
758 // Process DB Query
759 if (!cfg->dryrun) {
760 rv = database_insert(cfg, dbconn, ptemp, dataout);
761 if (rv) {
762 fstat->result = apr_psprintf(cfg->pool,
763 "Input line %d, Database Error",
764 fstat->linesparsed);
765 }
766 }
767 return rv;
768}
diff --git a/utility/logparse.h b/utility/logparse.h
new file mode 100644
index 0000000..7ca0958
--- /dev/null
+++ b/utility/logparse.h
@@ -0,0 +1,37 @@
1#ifndef LOGPARSE_H_
2#define LOGPARSE_H_
3
4#include "config.h"
5
6typedef apr_status_t (*parser_func_f)(apr_pool_t *p, config_t *cfg,
7 config_output_field_t *field, const char *value, const char **ret);
8
9struct parser_func_t {
10 parser_func_f func;
11 int pos;
12 void *data;
13 void ***linedata;
14};
15
16#define parser_get_linedata(f) (*f->linedata)[f->pos]
17
18#define parser_set_linedata(f, v) (*f->linedata)[f->pos] = v
19
20parser_func_t *parser_get_func(const char *name);
21
22void parser_init(apr_pool_t *p);
23
24void parser_find_logs(config_t *cfg);
25
26void parser_split_logs(config_t *cfg);
27
28apr_status_t parser_tokenize_line(const char *arg_str, char ***argv_out,
29 apr_pool_t *token_context);
30
31apr_status_t parser_parsefile(config_t *cfg, config_dbd_t *dbconn,
32 config_filestat_t *fstat);
33
34apr_status_t parser_processline(apr_pool_t *ptemp, config_t *cfg,
35 config_dbd_t *dbconn, config_filestat_t *line, char **argv, int argc);
36
37#endif /*LOGPARSE_H_*/
diff --git a/utility/mod_log_sql.conf b/utility/mod_log_sql.conf
new file mode 100644
index 0000000..911fc18
--- /dev/null
+++ b/utility/mod_log_sql.conf
@@ -0,0 +1,70 @@
1InputDirectory ./logs
2ErrorLog ./error_log
3
4DBDDriver mysql
5DBDParams "host=localhost;user=root;dbname=apache_log"
6Table access_log
7MachineID 7of9
8UseTransactions on
9LogLevel notice
10DryRun off
11Summary on
12
13SplitInput on
14#SplitCount 4
15SplitMinLines 0
16SplitMaxLines 50000
17SplitDirectory ./split_temp
18
19#ThreadCount 0
20
21BadLineFile ./badlines.log
22BadLineMax 10
23
24LogFormatConfig CLF remhost String
25LogFormatConfig CLF ident String
26LogFormatConfig CLF user String
27LogFormatConfig CLF date Date
28LogFormatConfig CLF request String
29LogFormatConfig CLF status Number
30LogFormatConfig CLF bytes_sent Number
31
32LogFormatConfig Combined remhost String
33LogFormatConfig Combined ident String
34LogFormatConfig Combined user String
35LogFormatConfig Combined date Date
36LogFormatConfig Combined request String
37LogFormatConfig Combined status Number
38LogFormatConfig Combined bytes_sent Number
39LogFormatConfig Combined referer String
40LogFormatConfig Combined agent String
41
42LogFormat Combined
43
44#Linefilter - "BAD"
45#LineFilter "GOOD"
46#LineFilter + "BETTER"
47# the next filter ignores ALL lines
48#LineFilter -
49#PreFilter request - "Rebuild"
50#PostFilter request_method "GET"
51
52# Usage field datatype(size) default source [function [param]...]
53OutputField bytes_sent int 0 bytes_sent
54OutputField request_protocol varchar(10) "" request regexmatch "(HTTP/[\\d\\.]+)$"
55OutputField remote_host varchar(50) "" remhost
56OutputField request_method varchar(25) "" request regexmatch "^(\\w+)"
57OutputField time_stamp int 0 date totimestamp
58OutputField status smallint 0 status
59OutputField request_line varchar(255) "" request
60#OutputField request_uri varchar(255) "" request regexmatch "^\\w+ (.+) \\w+/[\\d\\.]+$"
61OutputField remote_user varchar(50) "" user
62OutputField remote_logname varchar(50) "" ident
63OutputField request_time char(28) "" date wrap "[" "]"
64#Only used for Combined log input, if standard CLF input, they are ignored
65OutputField agent varchar(255) "" agent
66OutputField referer varchar(255) "" referer
67OutputField machine_id varchar(25) "" "" machineid
68#VIZU Fields
69OutputField poll_id int 0 request queryarg n "&;"
70
diff --git a/utility/shell.c b/utility/shell.c
new file mode 100644
index 0000000..1b9e890
--- /dev/null
+++ b/utility/shell.c
@@ -0,0 +1,342 @@
1#include "apr.h"
2#include "apr_getopt.h"
3#include "apr_tables.h"
4
5#define APR_WANT_STDIO
6#include "apr_want.h"
7#include "stdlib.h"
8
9#include "shell.h"
10#include "config.h"
11#include "logparse.h"
12#include "database.h"
13#include "util.h"
14
15#if APR_HAS_THREADS
16#include "apr_queue.h"
17#include "apr_thread_pool.h"
18
19static apr_queue_t *queue;
20
21void run_multithreaded(config_t *cfg);
22#endif
23
24void run_singlethreaded(config_t *cfg);
25
26const apr_getopt_option_t _opt_config[] = {
27 {"machineid", 'm', 1, "Machine ID for the log file"},
28 {"transaction", 't', 1, "Use a Transaction (yes,no)"},
29 {"logformat", 'r', 1, "Use this logformat to parse files"},
30 {"file", 'f', 1, "Parse this single log file (input dir is NOT scanned)"},
31 {"inputdir", 'i', 1, "Input Directory to look for log files"},
32 {"config", 'c', 1, "Configuration file to use (default mod_log_sql.conf)"},
33 {"dryrun", 'n', 0, "Perform a dry run (do not actually alter the databse)"},
34 {"dump", 'd', 0, "Dump the configuration after parsing and quit"},
35 {"loglevel", 'l', 1, "Log Level (deubg, notice, error)"},
36 {"summary", 's', 1, "Summary (yes,no)"},
37 {"threadcount", 'p', 1, "Set thread count (a number greater than 0)"},
38 {"help", 'h', 0, "Show Help"},
39 {NULL}
40};
41
42void show_help(const char *prog, const apr_getopt_option_t *opts, FILE *output)
43{
44 int ptr = 0;
45 fprintf(output, "Usage: %s [OPTIONS] [files...]\n\n", prog);
46 while (opts[ptr].optch != 0) {
47 if (opts[ptr].optch > 255) {
48 if (opts[ptr].name) {
49 fprintf(output, " --%-10s", opts[ptr].name);
50 } else {
51 fprintf(output, " ");
52 }
53 } else {
54 if (opts[ptr].name) {
55 fprintf(output, " -%c --%-10s", opts[ptr].optch, opts[ptr].name);
56 } else {
57 fprintf(output, " -%c ", opts[ptr].optch);
58 }
59 }
60 if (opts[ptr].has_arg) {
61 fprintf(output, " (arg)");
62 } else {
63 fprintf(output, " ");
64 }
65 fprintf(output, " %s\n", opts[ptr].description);
66 ptr++;
67 }
68}
69
70void print_summary(config_t *cfg) {
71 config_filestat_t *fstat;
72 int i,m;
73 apr_time_t totaltime = 0;
74 apr_size_t totalparsed = 0, totalskipped = 0, totalbad = 0;
75
76 fstat = (config_filestat_t *)cfg->input_files->elts;
77
78 printf("Execution Summary\nParsed %d files\n", cfg->input_files->nelts);
79 for (i=0, m=cfg->input_files->nelts; i<m; i++) {
80 totaltime += fstat[i].stop - fstat[i].start;
81 totalparsed += fstat[i].linesparsed;
82 totalskipped += fstat[i].lineskipped;
83 totalbad += fstat[i].linesbad;
84 printf(" File: %s\n"
85 " Lines Added %'d out of %'d (Skipped %'d, Bad %'d)\n"
86 " Status: %s\n"
87 " Duration: %02"APR_TIME_T_FMT":%02"APR_TIME_T_FMT".%"APR_TIME_T_FMT" (minutes, seconds, and miliseconds)\n"
88 "\n",
89 fstat[i].fname,
90 fstat[i].linesparsed - fstat[i].lineskipped - fstat[i].linesbad,
91 fstat[i].linesparsed,
92 fstat[i].lineskipped,
93 fstat[i].linesbad,
94 fstat[i].result,
95 apr_time_sec(fstat[i].stop - fstat[i].start)/60,
96 apr_time_sec(fstat[i].stop - fstat[i].start) % 60,
97 apr_time_msec(fstat[i].stop - fstat[i].start)
98 );
99 }
100 printf("Totals\n"
101 " Lines Added %'d out of %'d (Skipped %'d, Bad %'d)\n"
102 " Duration: %02"APR_TIME_T_FMT":%02"APR_TIME_T_FMT".%"APR_TIME_T_FMT" (minutes, seconds, and miliseconds)\n"
103 "\n",
104 totalparsed - totalskipped - totalbad,
105 totalparsed,
106 totalskipped,
107 totalbad,
108 apr_time_sec(totaltime)/60,
109 apr_time_sec(totaltime) % 60,
110 apr_time_msec(totaltime)
111 );
112}
113
114int main(int argc, const char *const argv[])
115{
116 apr_pool_t *pool, *ptemp;
117 apr_getopt_t *opts;
118 int opt;
119 const char *opt_arg;
120 apr_status_t rv;
121 apr_table_t *args;
122 config_t *cfg;
123
124 apr_app_initialize(&argc, &argv, NULL);
125 atexit(apr_terminate);
126
127 if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
128 fprintf(stderr, "Failed to create memory pool!\n");
129 exit(1);
130 }
131 apr_pool_create(&ptemp, NULL);
132
133 /** Iterate over command line arguments
134 * shoving args in a apr_table for processing later*/
135 args = apr_table_make(ptemp, 5);
136 apr_table_setn(args, "config", "mod_log_sql.conf");
137 apr_getopt_init(&opts, ptemp, argc, argv);
138 while ((rv = apr_getopt_long(opts, _opt_config, &opt, &opt_arg)) == APR_SUCCESS) {
139 switch (opt) {
140 case 'c':
141 apr_table_setn(args,"config",opt_arg);
142 break;
143 case 'd':
144 apr_table_setn(args,"dump","yes");
145 break;
146 case 'f':
147 apr_table_setn(args,"inputfile",opt_arg);
148 break;
149 case 'h':
150 show_help(argv[0], _opt_config, stdout);
151 exit(1);
152 break;
153 case 'i':
154 apr_table_setn(args,"inputdirectory",opt_arg);
155 break;
156 case 'l':
157 apr_table_setn(args,"loglevel",opt_arg);
158 break;
159 case 'm':
160 apr_table_setn(args,"machineid",opt_arg);
161 break;
162 case 'n':
163 apr_table_setn(args,"dryrun","yes");
164 break;
165 case 'p':
166 apr_table_setn(args,"threadcount",opt_arg);
167 break;
168 case 'r':
169 apr_table_setn(args,"logformat",opt_arg);
170 break;
171 case 's':
172 apr_table_setn(args,"summary",opt_arg);
173 break;
174 case 't':
175 apr_table_setn(args,"usetransactions",opt_arg);
176 break;
177 }
178 }
179 if (rv != APR_EOF) {
180 show_help(argv[0], _opt_config, stderr);
181 exit(1);
182 }
183 // Check if no extra args were passed
184 if (opts->ind != opts->argc) {
185 show_help(argv[0], _opt_config, stderr);
186 fprintf(stderr, "\n%s: Extra unknown arguments passed\n\n",argv[0]);
187 exit(1);
188 }
189
190 // Initialize sub systems
191 parser_init(pool);
192 config_init(pool);
193 database_init(pool);
194 // Process configuration file
195 cfg = config_create(pool);
196 // initialize STD out error log
197 logging_preinit(cfg);
198 rv = config_read(cfg, apr_table_get(args,"Config"), args);
199 apr_pool_destroy(ptemp);
200
201 // Initialize Log system AFTER we parse the configuration
202 logging_init(cfg);
203
204 if (APR_STATUS_IS_ENOENT(rv)) {
205 logging_log(cfg,LOGLEVEL_NOISE,"Could not load configuration file: %s",apr_table_get(args,"config"));
206 } else if (rv) {
207 exit(1);
208 }
209 if (cfg->dump) {
210 config_dump(cfg);
211 exit(0);
212 }
213
214 if (config_check(cfg)) {
215 logging_log(cfg,LOGLEVEL_NOISE, "Please correct the configuration");
216 exit(1);
217 }
218
219 // Only Find files IF no filename was passed via the command line
220 if (apr_is_empty_array(cfg->input_files)) {
221 parser_find_logs(cfg);
222 }
223 if (!apr_is_empty_array(cfg->input_files)) {
224 parser_split_logs(cfg);
225#if APR_HAS_THREADS
226 if (cfg->thread_count > 0) {
227 run_multithreaded(cfg);
228 } else {
229#endif
230 run_singlethreaded(cfg);
231#if APR_HAS_THREADS
232 }
233#endif
234 } else {
235 logging_log(cfg,LOGLEVEL_NOISE,"No log files found to parse");
236 }
237
238 if (cfg->summary) {
239 print_summary(cfg);
240 }
241 return 0;
242}
243
244void run_singlethreaded(config_t *cfg)
245{
246 config_filestat_t *filelist;
247 config_dbd_t *dbconn = NULL;
248 int f, l;
249 apr_status_t rv;
250
251 if (!cfg->dryrun) {
252 if ((rv = database_connect(cfg, &dbconn))) {
253 logging_log(cfg,LOGLEVEL_NOISE, "Error Connecting to Database");
254 exit(1);
255 }
256 }
257
258 filelist = (config_filestat_t *)cfg->input_files->elts;
259 for (f=0, l=cfg->input_files->nelts; f < l; f++) {
260 rv = parser_parsefile(cfg, dbconn, &filelist[f]);
261 if (rv) {
262 logging_log(cfg, LOGLEVEL_NOISE,
263 "Error occured parsing log files. Aborting");
264 break;
265 }
266 }
267
268 if (!cfg->dryrun) {
269 database_disconnect(dbconn);
270 }
271}
272
273#if APR_HAS_THREADS
274void * APR_THREAD_FUNC run_filethread(apr_thread_t *thd, void *data)
275{
276 config_t *cfg = data;
277 config_dbd_t *dbconn = NULL;
278 config_filestat_t *fileentry;
279 apr_status_t rv;
280
281 if (!cfg->dryrun) {
282 if ((rv = database_connect(cfg, &dbconn))) {
283 logging_log(cfg,LOGLEVEL_NOISE, "Error Connecting to Database");
284 return NULL;
285 }
286 }
287
288 while (1) {
289 rv = apr_queue_pop(queue, (void **)&fileentry);
290 if (rv == APR_EINTR)
291 continue;
292 if (rv == APR_EOF)
293 break;
294 rv = parser_parsefile(cfg, dbconn, fileentry);
295 if (rv) {
296 logging_log(cfg, LOGLEVEL_NOISE,
297 "Error occured parsing log file %s", fileentry->fname);
298 }
299 }
300
301 if (!cfg->dryrun) {
302 database_disconnect(dbconn);
303 }
304 return NULL;
305}
306
307void run_multithreaded(config_t *cfg)
308{
309 logging_log(cfg, LOGLEVEL_NOISE, "Running Multithreaded");
310
311 config_filestat_t *filelist;
312 int f, l;
313 apr_status_t rv;
314 apr_pool_t *tp;
315 apr_thread_pool_t *thrp;
316 unsigned int count;
317
318 apr_pool_create(&tp, cfg->pool);
319 rv = apr_queue_create(&queue, cfg->input_files->nelts, tp);
320
321 rv = apr_thread_pool_create(&thrp, 0, cfg->thread_count, tp);
322
323 //populate queue
324 filelist = (config_filestat_t *)cfg->input_files->elts;
325 for (f=0, l=cfg->input_files->nelts; f < l; f++) {
326 rv = apr_queue_push(queue, &filelist[f]);
327 }
328 // populate the worker threads
329 for (f=0; f<cfg->thread_count; f++) {
330 rv = apr_thread_pool_push(thrp, run_filethread, cfg, 0, NULL);
331 }
332
333 do {
334 apr_sleep(apr_time_from_sec(1));
335 count = apr_queue_size(queue);
336 } while (count > 0);
337
338 rv = apr_queue_term(queue);
339
340 rv = apr_thread_pool_destroy(thrp);
341}
342#endif
diff --git a/utility/shell.h b/utility/shell.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/utility/shell.h
diff --git a/utility/util.c b/utility/util.c
new file mode 100644
index 0000000..dcfb028
--- /dev/null
+++ b/utility/util.c
@@ -0,0 +1,189 @@
1#include "util.h"
2#include "apr_strings.h"
3#include "apr_lib.h"
4#include "apr_file_io.h"
5#include "apr_time.h"
6
7#include "config.h"
8
9#include <stdarg.h>
10
11char *lowerstr(apr_pool_t *pool, const char *input)
12{
13 char *temp;
14 char *itr;
15 temp = apr_pstrdup(pool, input);
16 for (itr=temp; *itr!='\0'; itr++) {
17 *itr = apr_tolower(*itr);
18 }
19 return temp;
20}
21
22void line_chomp(char *str)
23{
24 int len;
25 // chomp off newline
26 len = strlen(str);
27 if (len) {
28 while (str[len-1] == '\r' || str[len-1] == '\n') {
29 str[len-1] = '\0';
30 len--;
31 }
32 }
33}
34
35/*
36 * *** Ripped from HTTPD util.c (why are so many PORTABLE things not in APR UTIL?)
37 */
38static char x2c(const char *what)
39{
40 register char digit;
41
42 digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10
43 : (what[0] - '0'));
44 digit *= 16;
45 digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10
46 : (what[1] - '0'));
47 return (digit);
48}
49
50/*
51 * *** Ripped from HTTPD util.c (why are so many PORTABLE things not in APR UTIL?)
52 *
53 * Unescapes a URL, leaving reserved characters intact.
54 * Returns 0 on success, non-zero on error
55 * Failure is due to
56 * bad % escape returns HTTP_BAD_REQUEST
57 *
58 * decoding %00 or a forbidden character returns HTTP_NOT_FOUND
59 */
60static int unescape_url(char *url, const char *forbid, const char *reserved)
61{
62 register int badesc, badpath;
63 char *x, *y;
64
65 badesc = 0;
66 badpath = 0;
67 /* Initial scan for first '%'. Don't bother writing values before
68 * seeing a '%' */
69 y = strchr(url, '%');
70 if (y == NULL) {
71 return APR_SUCCESS;
72 }
73 for (x = y; *y; ++x, ++y) {
74 if (*y != '%') {
75 *x = *y;
76 }
77 else {
78 if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) {
79 badesc = 1;
80 *x = '%';
81 }
82 else {
83 char decoded;
84 decoded = x2c(y + 1);
85 if ((decoded == '\0')
86 || (forbid && strchr(forbid, decoded))) {
87 badpath = 1;
88 *x = decoded;
89 y += 2;
90 }
91 else if (reserved && strchr(reserved, decoded)) {
92 *x++ = *y++;
93 *x++ = *y++;
94 *x = *y;
95 }
96 else {
97 *x = decoded;
98 y += 2;
99 }
100 }
101 }
102 }
103 *x = '\0';
104 if (badesc) {
105 return APR_EINVAL;
106 }
107 else if (badpath) {
108 return APR_EINVAL;
109 }
110 else {
111 return APR_SUCCESS;
112 }
113}
114
115/*
116 * *** Ripped from HTTPD util.c (why are so many PORTABLE things not in APR UTIL?)
117 */
118int ap_unescape_url(char *url)
119{
120 /* Traditional */
121#ifdef CASE_BLIND_FILESYSTEM
122 return unescape_url(url, "/\\", NULL);
123#else
124 return unescape_url(url, "/", NULL);
125#endif
126}
127
128void logging_preinit(config_t *cfg)
129{
130 apr_pool_create(&cfg->errorlog_p, cfg->pool);
131 apr_file_open_stderr(&cfg->errorlog_fperr, cfg->pool);
132}
133
134void logging_init(config_t *cfg)
135{
136 apr_status_t rv;
137 if (cfg->errorlog) {
138 rv = apr_file_open(&cfg->errorlog_fp, cfg->errorlog,
139 APR_FOPEN_CREATE | APR_FOPEN_WRITE | APR_FOPEN_APPEND,
140 APR_OS_DEFAULT,
141 cfg->pool);
142 if (rv) {
143 printf("Error opening %s\n",cfg->errorlog);
144 cfg->loglevel = LOGLEVEL_NONE;
145 }
146 logging_log(cfg, LOGLEVEL_ERROR, "Log file Opened");
147 } else {
148 cfg->loglevel = LOGLEVEL_NONE;
149 logging_log(cfg, LOGLEVEL_NOISE, "No Log file specified, disabled logging");
150 }
151}
152
153const char *logging_strerror(apr_status_t rv)
154{
155 char buff[256];
156 return apr_strerror(rv, buff, 256);
157}
158
159void logging_log(config_t *cfg, loglevel_e level, const char *fmt, ...)
160{
161 va_list ap;
162 char date[APR_RFC822_DATE_LEN];
163 struct iovec vec[4];
164 apr_size_t blen;
165
166 if (cfg->loglevel < level) return;
167
168 va_start(ap, fmt);
169 apr_pool_clear(cfg->errorlog_p);
170
171 apr_rfc822_date(date, apr_time_now());
172 vec[0].iov_base = date;
173 vec[0].iov_len = APR_RFC822_DATE_LEN-1;
174 vec[1].iov_base = " ";
175 vec[1].iov_len = 2;
176 vec[2].iov_base = apr_pvsprintf(cfg->errorlog_p, fmt, ap);
177 vec[2].iov_len = strlen(vec[2].iov_base);
178 vec[3].iov_base = "\n";
179 vec[3].iov_len = 1;
180
181 if (level == LOGLEVEL_NOISE) {
182 apr_file_writev(cfg->errorlog_fperr,&vec[2],2,&blen);
183 }
184 if (cfg->loglevel > LOGLEVEL_NONE && cfg->errorlog_fp) {
185 apr_file_writev(cfg->errorlog_fp,vec,4,&blen);
186 }
187
188 va_end(ap);
189}
diff --git a/utility/util.h b/utility/util.h
new file mode 100644
index 0000000..a5a7f7e
--- /dev/null
+++ b/utility/util.h
@@ -0,0 +1,26 @@
1#ifndef UTIL_H_
2#define UTIL_H_
3
4#include "apr_pools.h"
5
6#include "config.h"
7
8char *lowerstr(apr_pool_t *pool, const char *input);
9
10/**
11 * Chomp new line characters off the end of the line
12 */
13void line_chomp(char *str);
14
15int ap_unescape_url(char *url);
16
17void logging_preinit(config_t *cfg);
18
19void logging_init(config_t *cfg);
20
21void logging_log(config_t *cfg, loglevel_e level, const char *fmt, ...)
22 __attribute__((format(printf, 3, 4)));
23
24const char *logging_strerror(apr_status_t rv);
25
26#endif /*UTIL_H_*/