├── .gitignore ├── DEBIAN.md ├── LICENSE ├── README.md ├── dist ├── centos7 │ └── Dockerfile ├── data.yml ├── debian10 │ └── Dockerfile ├── debian7 │ └── Dockerfile ├── debian8 │ └── Dockerfile ├── debian9 │ └── Dockerfile ├── files │ ├── centos │ │ └── 02-gelf.conf │ ├── debian │ │ ├── log_gelf.conf │ │ └── log_gelf.load │ └── ubuntu │ │ ├── log_gelf.conf │ │ └── log_gelf.load ├── recipe.rb ├── tools.rb └── ubuntu1404 │ └── Dockerfile └── src ├── .deps ├── Makefile ├── apache20.h ├── functions.h ├── functions20.h ├── mod_log_gelf.c ├── mod_log_gelf.h └── modules.mk /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | .libs/ 13 | *.lib 14 | *.a 15 | *.la 16 | *.lo 17 | *.slo 18 | 19 | # Shared objects (inc. Windows DLLs) 20 | *.dll 21 | *.so 22 | *.so.* 23 | *.dylib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | *.i*86 30 | *.x86_64 31 | *.hex 32 | 33 | # Debug files 34 | *.dSYM/ 35 | 36 | # Packages 37 | dist/pkg/ 38 | dist/cache/ 39 | -------------------------------------------------------------------------------- /DEBIAN.md: -------------------------------------------------------------------------------- 1 | 2 | # make debian deb-packages with docker 3 | 4 | ## Install Docker 5 | 6 | * apt install docker.io git 7 | 8 | ## Build base image for package building 9 | 10 | * git clone this repository 11 | * cd to the root of this repository 12 | * choose wanted Debian release 13 | 14 | ``` 15 | $ docker build -t apache-gelf-debian7 dist/debian7/ 16 | $ docker build -t apache-gelf-debian8 dist/debian8/ 17 | $ docker build -t apache-gelf-debian9 dist/debian9/ 18 | $ docker build -t apache-gelf-debian10 dist/debian10/ 19 | ``` 20 | 21 | ## Build deb -binary package using above built image 22 | 23 | * clean build environment from previous runs (if any) 24 | 25 | ``` 26 | $ rm -r dist/cache dist/tmp-* 27 | ``` 28 | 29 | * run one of the below depending on the chosen Debian release 30 | 31 | ``` 32 | $ docker run --rm=true -v `pwd`:/apache-gelf -t -i apache-gelf-debian7 fpm-cook package /apache-gelf/dist/recipe.rb 33 | $ docker run --rm=true -v `pwd`:/apache-gelf -t -i apache-gelf-debian8 fpm-cook package /apache-gelf/dist/recipe.rb 34 | $ docker run --rm=true -v `pwd`:/apache-gelf -t -i apache-gelf-debian9 fpm-cook package /apache-gelf/dist/recipe.rb 35 | $ docker run --rm=true -v `pwd`:/apache-gelf -t -i apache-gelf-debian10 fpm-cook package /apache-gelf/dist/recipe.rb 36 | ``` 37 | 38 | * built deb -file is in dist/pkg/ -folder 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apache-mod_log_gelf 2 | ### (BETA, not tested in production environments!) 3 | Apache2 module for writing access logs to Graylog 4 | 5 | # Install system package 6 | Download a package for your operating system from [here](https://github.com/Graylog2/apache-mod_log_gelf/releases) 7 | Update Apache2 to the latests version and use `mpm_prefork`. 8 | 9 | Ubuntu: 10 | 11 | ``` 12 | $ sudo apt-get update 13 | $ sudo apt-get upgrade 14 | $ sudo a2enmod mpm_prefork 15 | $ sudo apt-get install libjson-c2 zlib1g 16 | $ sudo dpkg -i libapache2-mod-gelf_0.1.0-1_amd64.deb 17 | $ sudo a2enmod log_gelf 18 | restart apache 19 | ``` 20 | 21 | Older Debian systems need installed backports repository in order to install `libjson-c2`: 22 | 23 | ``` 24 | $ echo 'deb http://http.debian.net/debian wheezy-backports main' >> /etc/apt/sources.list 25 | $ sudo apt-get update 26 | $ sudo apt-get upgrade 27 | $ sudo a2enmod mpm_prefork 28 | $ sudo apt-get install libjson-c2 zlib1g 29 | $ sudo dpkg -i libapache2-mod-gelf_0.1.0-1_amd64.deb 30 | $ sudo a2enmod log_gelf 31 | restart apache 32 | ``` 33 | 34 | CentOS (>= 7): 35 | 36 | ``` 37 | $ sudo yum install json-c zlib 38 | $ sudo rpm -i libapache2-mod-gelf-0.1.0-1.x86_64.rpm 39 | restart apache 40 | ``` 41 | 42 | FreeBSD: 43 | 44 | ``` 45 | $ pkg install gmake 46 | $ pkg install json-c 47 | $ gmake && sudo gmake install 48 | restart apache 49 | ``` 50 | 51 | # Configuration 52 | 53 | Load the module in `/etc/apache2/mods-enabled/log_gelf.load`: 54 | 55 | ``` 56 | LoadModule log_gelf_module /usr/lib/apache2/modules/mod_log_gelf.so 57 | ``` 58 | 59 | Configure the module in `/etc/apache2/mods-enabled/log_gelf.conf`: 60 | 61 | ``` 62 | GelfEnabled On 63 | GelfUrl "udp://192.168.1.1:12201" 64 | GelfSource "hostname" 65 | GelfFacility "apache-gelf" 66 | GelfTag "gelf-tag" 67 | GelfCookie "tracking" 68 | GelfFields "ABDhmsvRti" 69 | ``` 70 | On CentOS both files are combined in `/etc/httpd/conf.modules.d/02-gelf.conf` 71 | 72 | | Parameter | Argument | Description | 73 | |--------------|------------------------|--------------------------------------------------------| 74 | | GelfEnabled | On/Off | Load GELF logging module | 75 | | GelfUrl | Graylog server URL | Set IP and port of a UDP GELF input | 76 | | GelfSource | (Optional) | Overwrite source field | 77 | | GelfFacility | (Optional) | Overwrite logging facility | 78 | | GelfTag | (Optional) | Add a `tag` field to every log message | 79 | | GelfCookie | (Optional) cookie name | Extract one cookie from web request, Use 'c' GelfField | 80 | | GelfHeader | (Optional) header name | Extract one header from web request, Use 'X' GelfField | 81 | | GelfFields | (Optional) | Configures which information should be logged | 82 | 83 | What does the `GelfFields` string mean: 84 | 85 | | Character | Logging information | 86 | |-----------|---------------------------------------------| 87 | | A | Agent string | 88 | | a | Request arguments | 89 | | B | Bytes send | 90 | | C | Connection status | 91 | | c | Extract Cookie (name must be in GelfCookie) | 92 | | D | Request duration (in microseconds) | 93 | | f | Requested file | 94 | | H | Protocol | 95 | | h | Remote host | 96 | | i | Remote address | 97 | | L | Local address | 98 | | l | Auth login name | 99 | | m | Request methode | 100 | | p | Server port | 101 | | P | Child PID | 102 | | R | Referer | 103 | | r | Request string | 104 | | s | Return status | 105 | | t | Request timestamp | 106 | | U | Request URI | 107 | | u | Username | 108 | | V | Server name | 109 | | v | VirtualHost name | 110 | | X | Extract Header (name must be in GelfHeader) | 111 | 112 | # Packages 113 | 114 | Build Docker base images: 115 | 116 | ``` 117 | $ docker build -t apache-gelf-ubuntu dist/ubuntu1404/ 118 | $ docker build -t apache-gelf-debian dist/debian7/ 119 | $ docker build -t apache-gelf-centos dist/centos7/ 120 | ``` 121 | 122 | Bundle module and configuration files to system package, e.g. for Ubuntu: 123 | 124 | ``` 125 | $ docker run --rm=true -v `pwd`:/apache-gelf -t -i apache-gelf-ubuntu fpm-cook package /apache-gelf/dist/recipe.rb 126 | ``` 127 | 128 | # Compile 129 | 130 | Install dependent c libraries: 131 | 132 | ``` 133 | $ sudo apt-get install apache2-dev libjson-c-dev zlib1g-dev 134 | ``` 135 | 136 | Compile and install modules: 137 | 138 | ``` 139 | $ cd src 140 | $ make 141 | $ sudo make install 142 | ``` 143 | 144 | # License 145 | 146 | Copyright (C) 2015 Graylog, Inc. 147 | 148 | Licensed under the Apache License, Version 2.0 (the "License"); 149 | you may not use this file except in compliance with the License. 150 | You may obtain a copy of the License at 151 | 152 | http://www.apache.org/licenses/LICENSE-2.0 153 | 154 | Unless required by applicable law or agreed to in writing, software 155 | distributed under the License is distributed on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 157 | See the License for the specific language governing permissions and 158 | limitations under the License. 159 | -------------------------------------------------------------------------------- /dist/centos7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:centos7 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN yum clean all 6 | RUN yum install -y rubygems ruby-devel make gcc tar rpm-build curl 7 | RUN yum install -y httpd httpd-devel json-c-devel zlib-devel 8 | RUN gem install fpm-cookery --no-ri --no-rdoc 9 | 10 | # Remove cached packages and metadata to keep images small. 11 | RUN yum clean all 12 | -------------------------------------------------------------------------------- /dist/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | default: 3 | version: "0.3.0" 4 | revision: "1" 5 | homepage: "https://www.graylog.org/" 6 | maintainer: "Graylog, Inc. " 7 | vendor: "graylog" 8 | license: "Apache v2.0" 9 | -------------------------------------------------------------------------------- /dist/debian10/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y ruby ruby-dev build-essential curl lsb-release \ 7 | && apt-get install -y apache2-dev libjson-c-dev zlib1g-dev \ 8 | && gem install fpm-cookery --no-ri --no-rdoc \ 9 | && apt-get clean 10 | 11 | -------------------------------------------------------------------------------- /dist/debian7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:wheezy 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN echo "deb http://archive.debian.org/debian wheezy main" > /etc/apt/sources.list \ 6 | && echo "deb http://archive.debian.org/debian-security wheezy/updates main" >> /etc/apt/sources.list \ 7 | && echo "deb http://archive.debian.org/debian wheezy-backports main" > /etc/apt/sources.list.d/backports.list \ 8 | && apt-get update -o Acquire::Check-Valid-Until=false \ 9 | && apt-get install -y ruby ruby-dev build-essential curl lsb-release \ 10 | && apt-get install -y apache2-threaded-dev libjson-c-dev zlib1g-dev \ 11 | && gem install -v 1.10 ffi --no-ri --no-rdoc \ 12 | && gem install fpm-cookery --no-ri --no-rdoc \ 13 | && apt-get clean 14 | 15 | -------------------------------------------------------------------------------- /dist/debian8/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y ruby ruby-dev build-essential curl lsb-release \ 7 | && apt-get install -y apache2-threaded-dev libjson-c-dev zlib1g-dev \ 8 | && gem install facter --no-ri --no-rdoc \ 9 | && gem install fpm-cookery --no-ri --no-rdoc \ 10 | && apt-get clean 11 | 12 | -------------------------------------------------------------------------------- /dist/debian9/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y ruby ruby-dev build-essential curl lsb-release \ 7 | && apt-get install -y apache2-dev libjson-c-dev zlib1g-dev \ 8 | && gem install fpm-cookery --no-ri --no-rdoc \ 9 | && apt-get clean 10 | 11 | -------------------------------------------------------------------------------- /dist/files/centos/02-gelf.conf: -------------------------------------------------------------------------------- 1 | LoadModule log_gelf_module /usr/lib64/httpd/modules/mod_log_gelf.so 2 | GelfEnabled On 3 | GelfUrl "udp://192.168.1.1:12201" 4 | -------------------------------------------------------------------------------- /dist/files/debian/log_gelf.conf: -------------------------------------------------------------------------------- 1 | GelfEnabled On 2 | GelfUrl "udp://192.168.1.1:12201" 3 | -------------------------------------------------------------------------------- /dist/files/debian/log_gelf.load: -------------------------------------------------------------------------------- 1 | LoadModule log_gelf_module /usr/lib/apache2/modules/mod_log_gelf.so 2 | -------------------------------------------------------------------------------- /dist/files/ubuntu/log_gelf.conf: -------------------------------------------------------------------------------- 1 | GelfEnabled On 2 | GelfUrl "udp://192.168.1.1:12201" 3 | -------------------------------------------------------------------------------- /dist/files/ubuntu/log_gelf.load: -------------------------------------------------------------------------------- 1 | LoadModule log_gelf_module /usr/lib/apache2/modules/mod_log_gelf.so 2 | -------------------------------------------------------------------------------- /dist/recipe.rb: -------------------------------------------------------------------------------- 1 | require_relative 'tools' 2 | 3 | class ApacheGelf < FPM::Cookery::Recipe 4 | include Tools 5 | 6 | description 'Apache2 GELF log module' 7 | 8 | name 'libapache2-mod-gelf' 9 | version data.version 10 | revision data.revision 11 | homepage data.homepage 12 | arch 'amd64' 13 | 14 | maintainer data.maintainer 15 | vendor data.vendor 16 | license data.license 17 | 18 | source 'file:///apache-gelf/src' 19 | 20 | platforms [:ubuntu] do 21 | section 'net' 22 | depends 'apache2', 'libjson-c2', 'zlib1g' 23 | build_depends 'apache2-dev', 'libjson-c-dev', 'zlib1g-dev' 24 | 25 | config_files '/etc/apache2/mods-available/log_gelf.load', 26 | '/etc/apache2/mods-available/log_gelf.conf' 27 | end 28 | 29 | platforms [:debian] do 30 | section 'net' 31 | 32 | case FPM::Cookery::Facts.osmajorrelease 33 | when '7' 34 | depends 'apache2', 'libjson-c2', 'zlib1g' 35 | build_depends 'apache2-threaded-dev', 'libjson-c-dev', 'zlib1g-dev' 36 | when '8' 37 | depends 'apache2', 'libjson-c2', 'zlib1g' 38 | build_depends 'apache2-threaded-dev', 'libjson-c-dev', 'zlib1g-dev' 39 | else 40 | depends 'apache2', 'libjson-c3', 'zlib1g' 41 | build_depends 'apache2-dev', 'libjson-c-dev', 'zlib1g-dev' 42 | end 43 | 44 | config_files '/etc/apache2/mods-available/log_gelf.load', 45 | '/etc/apache2/mods-available/log_gelf.conf' 46 | end 47 | 48 | platforms [:centos] do 49 | section 'net' 50 | depends 'httpd', 'json-c', 'zlib' 51 | build_depends 'httpd-devel', 'json-c-devel', 'zlib-devel' 52 | 53 | config_files '/etc/httpd/conf.modules.d/02-gelf.conf' 54 | end 55 | 56 | def build 57 | FileUtils.touch(File.join(builddir, '.deps')) 58 | make :clean 59 | make 60 | end 61 | 62 | def install 63 | case FPM::Cookery::Facts.platform 64 | when :ubuntu, :debian 65 | etc('apache2/mods-available').install osfile('log_gelf.load'), 'log_gelf.load' 66 | etc('apache2/mods-available').install osfile('log_gelf.conf'), 'log_gelf.conf' 67 | when :centos 68 | etc('httpd/conf.modules.d').install osfile('02-gelf.conf'), '02-gelf.conf' 69 | end 70 | 71 | make :install, 'DESTDIR' => destdir 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /dist/tools.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Tools 4 | def self.included(base) 5 | base.extend(ClassMethods) 6 | end 7 | 8 | module ClassMethods 9 | def fact(key) 10 | Facter.fact(key).value 11 | rescue NoMethodError 12 | raise "No fact for: #{key}" 13 | end 14 | 15 | def os 16 | os = fact('operatingsystem').downcase 17 | 18 | case os 19 | when 'centos' 20 | 'el' 21 | else 22 | os 23 | end 24 | end 25 | 26 | def osrel 27 | osrel = fact('operatingsystemrelease').downcase 28 | 29 | case os 30 | when 'debian', 'centos', 'el' 31 | osrel.split('.').first 32 | else 33 | osrel 34 | end 35 | end 36 | 37 | def sigar_cleanup(path) 38 | Dir["#{path}/*"].each do |file| 39 | unless file.end_with?('.so') 40 | FileUtils.rm(file) 41 | end 42 | 43 | if file =~ /freebsd|aix|solaris/ 44 | FileUtils.rm(file) 45 | end 46 | 47 | if file =~ /(ppc.*|ia64|s390x)-linux/ 48 | FileUtils.rm(file) 49 | end 50 | end 51 | end 52 | end 53 | 54 | def fact(key) 55 | self.class.fact(key) 56 | end 57 | 58 | def os 59 | self.class.os 60 | end 61 | 62 | def osrel 63 | self.class.osrel 64 | end 65 | 66 | def sigar_cleanup(path) 67 | self.class.sigar_cleanup(path) 68 | end 69 | 70 | def osfile(name) 71 | workdir(File.join('files', FPM::Cookery::Facts.platform.to_s, name)) 72 | end 73 | 74 | def file(name) 75 | workdir(File.join('files', name)) 76 | end 77 | 78 | end 79 | 80 | # WOW, monkeypatch! 81 | # 82 | # * Adds data method to recipe 83 | # * Calls after_build_package(output) on recipe if it exists. 84 | module FPM 85 | module Cookery 86 | class Recipe 87 | class RecipeData 88 | def initialize(recipe) 89 | @yaml = YAML.load_file(File.expand_path('../data.yml', __FILE__)) 90 | @recipe = recipe 91 | end 92 | 93 | def version 94 | data('version') 95 | end 96 | 97 | def version_major 98 | data('version_major') 99 | end 100 | 101 | def revision 102 | data('revision') 103 | end 104 | 105 | def source 106 | data('source') 107 | end 108 | 109 | def sha256 110 | data('sha256') 111 | end 112 | 113 | def homepage 114 | data('homepage') 115 | end 116 | 117 | def maintainer 118 | data('maintainer') 119 | end 120 | 121 | def vendor 122 | data('vendor') 123 | end 124 | 125 | def license 126 | data('license') 127 | end 128 | 129 | private 130 | 131 | def data(key) 132 | data = @yaml['default'].merge(@yaml.fetch(@recipe.name, {})) 133 | pattern = /#\{(\S+?)\}/ 134 | 135 | data[key].gsub(pattern) do |match| 136 | if match =~ pattern 137 | if @recipe.respond_to?($1) 138 | @recipe.public_send($1) 139 | elsif data.has_key?($1) 140 | data.fetch($1) 141 | else 142 | raise "No replacement for #{$1} found, abort." 143 | end 144 | end 145 | end 146 | end 147 | end 148 | 149 | def self.data 150 | RecipeData.new(self) 151 | end 152 | 153 | def data 154 | self.class.data 155 | end 156 | end 157 | 158 | class Packager 159 | def build_package(recipe, config) 160 | recipe.pkgdir.mkdir 161 | Dir.chdir(recipe.pkgdir) do 162 | version = FPM::Cookery::Package::Version.new(recipe, @target, config) 163 | maintainer = FPM::Cookery::Package::Maintainer.new(recipe, config) 164 | 165 | input = recipe.input(config) 166 | 167 | input.version = version 168 | input.maintainer = maintainer.to_s 169 | input.vendor = version.vendor if version.vendor 170 | input.epoch = version.epoch if version.epoch 171 | 172 | add_scripts(recipe, input) 173 | #remove_excluded_files(recipe) 174 | 175 | output_class = FPM::Package.types[@target] 176 | 177 | output = input.convert(output_class) 178 | 179 | begin 180 | output.output(output.to_s) 181 | 182 | if recipe.respond_to?(:after_build_package) 183 | recipe.after_build_package(output) 184 | end 185 | rescue FPM::Package::FileAlreadyExists 186 | Log.info "Removing existing package file: #{output.to_s}" 187 | FileUtils.rm_f(output.to_s) 188 | retry 189 | ensure 190 | input.cleanup if input 191 | output.cleanup if output 192 | Log.info "Created package: #{File.join(Dir.pwd, output.to_s)}" 193 | end 194 | end 195 | end 196 | end 197 | end 198 | end 199 | -------------------------------------------------------------------------------- /dist/ubuntu1404/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | MAINTAINER Graylog Inc. 4 | 5 | RUN apt-get clean 6 | RUN apt-get update 7 | RUN apt-get install -y ruby1.9.1 ruby1.9.1-dev build-essential curl lsb-release 8 | RUN apt-get install -y apache2 apache2-dev libjson-c-dev zlib1g-dev 9 | RUN gem install fpm-cookery --no-ri --no-rdoc 10 | 11 | # Remove cached packages and metadata to keep images small. 12 | RUN apt-get clean 13 | -------------------------------------------------------------------------------- /src/.deps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graylog-labs/apache-mod_log_gelf/8042e84edfaf75dc20eb9ac4ff793927ca092360/src/.deps -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | builddir=. 2 | 3 | ifneq (,$(wildcard /usr/share/apache2/build/special.mk)) 4 | top_srcdir=/usr/share/apache2 5 | top_builddir=/usr/share/apache2 6 | include /usr/share/apache2/build/special.mk 7 | endif 8 | # apache2.4 on freebsd 9 | ifneq (,$(wildcard /usr/local/share/apache24/build/special.mk)) 10 | top_srcdir=/usr/local/share/apache24 11 | top_builddir=/usr/local/share/apache24 12 | include /usr/local/share/apache24/build/special.mk 13 | endif 14 | ifneq (,$(wildcard /usr/lib64/httpd/build/special.mk)) 15 | top_srcdir=/etc/httpd 16 | top_builddir=/usr/lib64/httpd 17 | include /usr/lib64/httpd/build/special.mk 18 | endif 19 | ifneq (,$(wildcard /etc/debian_version)) 20 | DISTRIBUTION := $(shell lsb_release -i | cut -f2) 21 | RELEASE := $(shell lsb_release --short --codename) 22 | ifeq ($(DISTRIBUTION),Debian) 23 | ifeq ($(RELEASE),wheezy) 24 | DEFS=-DWITH_APACHE22 25 | endif 26 | endif 27 | endif 28 | 29 | # the used tools 30 | APXS=apxs 31 | APACHECTL=apachectl 32 | 33 | # additional defines, includes and libraries 34 | LIBS=-ljson-c -lz 35 | 36 | # the default target 37 | all: local-shared-build 38 | 39 | # install the shared object file into Apache 40 | install: install-modules-yes 41 | 42 | # cleanup 43 | clean: 44 | -rm -f mod_log_gelf.o mod_log_gelf.lo mod_log_gelf.slo mod_log_gelf.la 45 | 46 | # simple test 47 | test: reload 48 | lynx -mime_header http://localhost 49 | 50 | # install and activate shared object by reloading Apache to 51 | # force a reload of the shared object file 52 | reload: install restart 53 | 54 | # the general Apache start/restart/stop 55 | # procedures 56 | start: 57 | $(APACHECTL) start 58 | restart: 59 | $(APACHECTL) restart 60 | stop: 61 | $(APACHECTL) stop 62 | 63 | -------------------------------------------------------------------------------- /src/apache20.h: -------------------------------------------------------------------------------- 1 | #ifndef APACHE20_H 2 | #define APACHE20_H 3 | 4 | #include "apr_strings.h" 5 | #include "apr_lib.h" 6 | #include "apr_hash.h" 7 | #include "apr_optional.h" 8 | #include "apr_reslist.h" 9 | #include "apr_signal.h" 10 | #define APR_WANT_STRFUNC 11 | #include "apr_want.h" 12 | #include "apr_tables.h" 13 | 14 | #include "ap_config.h" 15 | 16 | #include "httpd.h" 17 | #include "http_config.h" 18 | #include "http_core.h" 19 | #include "http_log.h" 20 | #include "http_protocol.h" 21 | 22 | #include "util_time.h" 23 | 24 | #define log_error ap_log_error 25 | 26 | #endif /* APACHE20_H */ 27 | -------------------------------------------------------------------------------- /src/functions.h: -------------------------------------------------------------------------------- 1 | /* Begin the individual functions that, given a request r, 2 | * extract the needed information from it and return the 3 | * value to the calling entity. 4 | */ 5 | 6 | json_object *extract_remote_host(request_rec *r, char *a) 7 | { 8 | return json_object_new_string(ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL)); 9 | } 10 | 11 | json_object *extract_remote_address(request_rec *r, char *a) __attribute__((unused)); 12 | 13 | json_object *extract_remote_address(request_rec *r, char *a) 14 | { 15 | #ifdef WITH_APACHE22 16 | return json_object_new_string(r->connection->remote_ip); 17 | #else 18 | return json_object_new_string(r->connection->client_ip); 19 | #endif 20 | } 21 | 22 | json_object *extract_local_address(request_rec *r, char *a) __attribute__((unused)); 23 | 24 | json_object *extract_local_address(request_rec *r, char *a) 25 | { 26 | return json_object_new_string(r->connection->local_ip); 27 | } 28 | 29 | json_object *extract_remote_logname(request_rec *r, char *a) 30 | { 31 | const char *rlogin = ap_get_remote_logname(r); 32 | if (rlogin == NULL) { 33 | return NULL; 34 | } else if (strlen(rlogin) == 0) { 35 | rlogin = "\"\""; 36 | } 37 | 38 | return json_object_new_string(rlogin); 39 | } 40 | 41 | json_object *extract_remote_user(request_rec *r, char *a) 42 | { 43 | #ifdef WITH_APACHE13 44 | char *rvalue = r->connection->user; 45 | #else 46 | char *rvalue = r->user; 47 | #endif 48 | if (rvalue == NULL) { 49 | rvalue = "-"; 50 | } else if (strlen(rvalue) == 0) { 51 | rvalue = "\"\""; 52 | } 53 | return json_object_new_string(rvalue); 54 | } 55 | 56 | json_object *extract_request_line(request_rec *r, char *a) 57 | { 58 | /* Upddated to mod_log_config logic */ 59 | /* NOTE: If the original request contained a password, we 60 | * re-write the request line here to contain XXXXXX instead: 61 | * (note the truncation before the protocol string for HTTP/0.9 requests) 62 | * (note also that r->the_request contains the unmodified request) 63 | */ 64 | return json_object_new_string( 65 | (r->parsed_uri.password) 66 | ? apr_pstrcat(r->pool, r->method, " ", 67 | apr_uri_unparse(r->pool, 68 | &r->parsed_uri, 0), 69 | r->assbackwards ? NULL : " ", 70 | r->protocol, NULL) 71 | : r->the_request); 72 | } 73 | 74 | json_object *extract_request_file(request_rec *r, char *a) 75 | { 76 | return json_object_new_string(r->filename); 77 | } 78 | 79 | json_object *extract_request_uri(request_rec *r, char *a) 80 | { 81 | return json_object_new_string(r->uri); 82 | } 83 | 84 | json_object *extract_request_method(request_rec *r, char *a) 85 | { 86 | return json_object_new_string(r->method); 87 | } 88 | 89 | json_object *extract_request_protocol(request_rec *r, char *a) 90 | { 91 | return json_object_new_string(r->protocol); 92 | } 93 | 94 | json_object *extract_request_query(request_rec *r, char *a) 95 | { 96 | return json_object_new_string((r->args) ? apr_pstrcat(r->pool, "?", 97 | r->args, NULL) 98 | : ""); 99 | } 100 | 101 | json_object *extract_status(request_rec *r, char *a) 102 | { 103 | if (r->status <= 0) { 104 | return NULL; 105 | } else { 106 | return json_object_new_int(r->status); 107 | } 108 | } 109 | 110 | json_object *extract_virtual_host(request_rec *r, char *a) 111 | { 112 | return json_object_new_string(r->server->server_hostname); 113 | } 114 | 115 | json_object *extract_server_name(request_rec *r, char *a) 116 | { 117 | return json_object_new_string(ap_get_server_name(r)); 118 | } 119 | 120 | json_object *extract_server_port(request_rec *r, char *a) 121 | { 122 | return json_object_new_string(apr_psprintf(r->pool, "%u", 123 | r->server->port ? r->server->port : ap_default_port(r))); 124 | } 125 | 126 | /* This respects the setting of UseCanonicalName so that 127 | * the dynamic mass virtual hosting trick works better. 128 | */ 129 | static const char *log_server_name(request_rec *r, char *a) __attribute__((unused)); 130 | static const char *log_server_name(request_rec *r, char *a) 131 | { 132 | return ap_get_server_name(r); 133 | } 134 | 135 | json_object *extract_child_pid(request_rec *r, char *a) 136 | { 137 | if (*a == '\0' || !strcmp(a, "pid")) { 138 | return json_object_new_string(apr_psprintf(r->pool, "%" APR_PID_T_FMT, getpid())); 139 | } 140 | else if (!strcmp(a, "tid")) { 141 | #if APR_HAS_THREADS 142 | apr_os_thread_t tid = apr_os_thread_current(); 143 | #else 144 | int tid = 0; /* APR will format "0" anyway but an arg is needed */ 145 | #endif 146 | return json_object_new_string(apr_psprintf(r->pool, "%pT", &tid)); 147 | } 148 | /* bogus format */ 149 | return json_object_new_string(a); 150 | } 151 | 152 | json_object *extract_header(request_rec *r, char *a) 153 | { 154 | const char *tempref; 155 | 156 | tempref = apr_table_get(r->headers_in, a); 157 | if (!tempref) 158 | { 159 | return NULL; 160 | } else { 161 | return json_object_new_string(tempref); 162 | } 163 | } 164 | 165 | json_object *extract_referer(request_rec *r, char *a) 166 | { 167 | const char *tempref; 168 | 169 | tempref = apr_table_get(r->headers_in, "Referer"); 170 | if (!tempref) 171 | { 172 | return NULL; 173 | } else { 174 | return json_object_new_string(tempref); 175 | } 176 | } 177 | 178 | json_object *extract_agent(request_rec *r, char *a) 179 | { 180 | const char *tempag; 181 | 182 | tempag = apr_table_get(r->headers_in, "User-Agent"); 183 | if (!tempag) 184 | { 185 | return NULL; 186 | } else { 187 | return json_object_new_string(tempag); 188 | } 189 | } 190 | 191 | json_object *extract_specific_cookie(request_rec *r, char *a) 192 | { 193 | const char *cookiestr; 194 | char *cookieend; 195 | char *isvalid; 196 | char *cookiebuf; 197 | 198 | if (a != NULL) { 199 | log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server, 200 | "watching for cookie '%s'", a); 201 | 202 | /* Fetch out the cookie header */ 203 | cookiestr = (char *)apr_table_get(r->headers_in, "cookie2"); 204 | if (cookiestr != NULL) { 205 | log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server, 206 | "Cookie2: [%s]", cookiestr); 207 | /* Does the cookie string contain one with our name? */ 208 | isvalid = ap_strstr_c(cookiestr, a); 209 | if (isvalid != NULL) { 210 | /* Move past the cookie name and equal sign */ 211 | isvalid += strlen(a) + 1; 212 | /* Duplicate it into the pool */ 213 | cookiebuf = apr_pstrdup(r->pool, isvalid); 214 | /* Segregate just this cookie out of the string 215 | * with a terminating nul at the first semicolon */ 216 | cookieend = ap_strchr(cookiebuf, ';'); 217 | if (cookieend != NULL) 218 | *cookieend = '\0'; 219 | return json_object_new_string(cookiebuf); 220 | } 221 | } 222 | 223 | cookiestr = (char *)apr_table_get(r->headers_in, "cookie"); 224 | if (cookiestr != NULL) { 225 | log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server, 226 | "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 json_object_new_string(cookiebuf); 235 | } 236 | } 237 | 238 | cookiestr = apr_table_get(r->headers_out, "set-cookie"); 239 | if (cookiestr != NULL) { 240 | log_error(APLOG_MARK,APLOG_DEBUG, 0, r->server, 241 | "Set-Cookie: [%s]", cookiestr); 242 | isvalid = ap_strstr_c(cookiestr, a); 243 | if (isvalid != NULL) { 244 | isvalid += strlen(a) + 1; 245 | cookiebuf = apr_pstrdup(r->pool, isvalid); 246 | cookieend = ap_strchr(cookiebuf, ';'); 247 | if (cookieend != NULL) 248 | *cookieend = '\0'; 249 | return json_object_new_string(cookiebuf); 250 | } 251 | } 252 | } 253 | 254 | return NULL; 255 | } 256 | -------------------------------------------------------------------------------- /src/functions20.h: -------------------------------------------------------------------------------- 1 | json_object *extract_bytes_sent(request_rec *r, char *a) 2 | { 3 | if (!r->sent_bodyct || !r->bytes_sent) { 4 | return NULL; 5 | } else { 6 | return json_object_new_int(r->bytes_sent); 7 | } 8 | } 9 | 10 | json_object *extract_request_time_custom(request_rec *r, char *a, 11 | apr_time_exp_t *xt) 12 | { 13 | apr_size_t retcode; 14 | char tstr[MAX_STRING_LEN]; 15 | apr_strftime(tstr, &retcode, sizeof(tstr), a, xt); 16 | return json_object_new_string(apr_pstrdup(r->pool, tstr)); 17 | } 18 | 19 | #define DEFAULT_REQUEST_TIME_SIZE 32 20 | typedef struct { 21 | unsigned t; 22 | char timestr[DEFAULT_REQUEST_TIME_SIZE]; 23 | unsigned t_validate; 24 | } cached_request_time; 25 | 26 | #define TIME_CACHE_SIZE 4 27 | #define TIME_CACHE_MASK 3 28 | static cached_request_time request_time_cache[TIME_CACHE_SIZE]; 29 | 30 | json_object *extract_request_time(request_rec *r, char *a) 31 | { 32 | apr_time_exp_t xt; 33 | 34 | /* Please read comments in mod_log_config.h for more info about 35 | * the I_INSIST....COMPLIANCE define 36 | */ 37 | if (a && *a) { /* Custom format */ 38 | #ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE 39 | ap_explode_recent_localtime(&xt, apr_time_now()); 40 | #else 41 | ap_explode_recent_localtime(&xt, r->request_time); 42 | #endif 43 | return extract_request_time_custom(r, a, &xt); 44 | } else { /* CLF format */ 45 | /* This code uses the same technique as ap_explode_recent_localtime(): 46 | * optimistic caching with logic to detect and correct race conditions. 47 | * See the comments in server/util_time.c for more information. 48 | */ 49 | cached_request_time* cached_time = apr_palloc(r->pool, 50 | sizeof(*cached_time)); 51 | #ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE 52 | apr_time_t request_time = apr_time_now(); 53 | #else 54 | apr_time_t request_time = r->request_time; 55 | #endif 56 | unsigned t_seconds = (unsigned)apr_time_sec(request_time); 57 | unsigned i = t_seconds & TIME_CACHE_MASK; 58 | memcpy(cached_time, &(request_time_cache[i]), sizeof(*cached_time)); 59 | if ((t_seconds != cached_time->t) || 60 | (t_seconds != cached_time->t_validate)) { 61 | 62 | /* Invalid or old snapshot, so compute the proper time string 63 | * and store it in the cache 64 | */ 65 | int timz; 66 | 67 | ap_explode_recent_localtime(&xt, r->request_time); 68 | timz = xt.tm_gmtoff; 69 | if (timz < 0) { 70 | timz = -timz; 71 | } 72 | cached_time->t = t_seconds; 73 | apr_snprintf(cached_time->timestr, DEFAULT_REQUEST_TIME_SIZE, 74 | "%d-%02d-%02dT%02d:%02d:%02d", 75 | xt.tm_year+1900, xt.tm_mon+1, xt.tm_mday, 76 | xt.tm_hour, xt.tm_min, xt.tm_sec); 77 | cached_time->t_validate = t_seconds; 78 | memcpy(&(request_time_cache[i]), cached_time, 79 | sizeof(*cached_time)); 80 | } 81 | return json_object_new_string(cached_time->timestr); 82 | } 83 | } 84 | 85 | json_object *extract_request_duration(request_rec *r, char *a) 86 | { 87 | apr_time_t duration = apr_time_now() - r->request_time; 88 | return json_object_new_int(apr_time_usec(duration)); 89 | } 90 | 91 | json_object *extract_connection_status(request_rec *r, char *a) __attribute__((unused)); 92 | json_object *extract_connection_status(request_rec *r, char *a) 93 | { 94 | if (r->connection->aborted) 95 | return json_object_new_string("X"); 96 | 97 | if (r->connection->keepalive == AP_CONN_KEEPALIVE && 98 | (!r->server->keep_alive_max || 99 | (r->server->keep_alive_max - r->connection->keepalives) > 0)) { 100 | return json_object_new_string("+"); 101 | } 102 | return NULL; 103 | } 104 | -------------------------------------------------------------------------------- /src/mod_log_gelf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifdef __linux__ 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "apache20.h" 12 | #include "functions.h" 13 | #include "functions20.h" 14 | 15 | #include "mod_log_gelf.h" 16 | 17 | #define DEFAULT_LOG_FMT "ABDhmsvRti" 18 | #define UDP 0 19 | #define TCP 1 20 | //#define RECONNECT_INTERVAL 120000000 // reconnect every 2min 21 | #define RECONNECT_INTERVAL 0 22 | #define MIN_CONNECTIONS 1 23 | #define MAX_CONNECTIONS 3 24 | #define SEND_BUFFER 1048576 25 | 26 | module AP_MODULE_DECLARE_DATA log_gelf_module; 27 | 28 | static int verbose = 0; 29 | static char errbuf[1024]; 30 | 31 | typedef struct { 32 | char key; /* item letter character */ 33 | item_func *extractor_func; /* its extraction function */ 34 | const char *arg; /* one string argument for func */ 35 | const char *field_name; /* GELF extra field name */ 36 | } log_item; 37 | 38 | typedef struct { 39 | apr_socket_t *gelf_sock; /* Actual GELF connection */ 40 | apr_sockaddr_t *gelf_addr; /* GELF Address */ 41 | } gelf_connection; 42 | 43 | typedef struct { 44 | int enabled; 45 | int port; /* GELF port */ 46 | int protocol; /* 0=UDP 1=TCP */ 47 | const char *server; /* Hostname/IP of Graylog server */ 48 | const char *source; /* Source field */ 49 | const char *facility; /* Facility field */ 50 | const char *tag; /* Optional tag field */ 51 | const char *fields; /* String with fields of interest */ 52 | const char *cookie; /* Log this cookie */ 53 | const char *header; /* Log this header */ 54 | log_item **parsed_fields; /* Link fields to extractor function */ 55 | apr_pool_t *parse_pool; /* memory pool for option parsing */ 56 | apr_reslist_t *connection_pool; /* Connection pool, with min, max and ttl settings */ 57 | int nmin; 58 | int nkeep; 59 | int nmax; 60 | apr_interval_time_t ttl; 61 | } gelf_config; 62 | 63 | static apr_array_header_t *log_item_list; 64 | 65 | /* module configuration */ 66 | static void *create_gelf_configuration(apr_pool_t *pool, server_rec *server) { 67 | gelf_config *config = apr_pcalloc(pool, sizeof(gelf_config)); 68 | config->source = "localhost"; 69 | config->facility = "apache-gelf"; 70 | config->fields = DEFAULT_LOG_FMT; 71 | /* connection pool settings */ 72 | config->nmin = MIN_CONNECTIONS; 73 | config->nkeep = MIN_CONNECTIONS; 74 | config->nmax = MAX_CONNECTIONS; 75 | config->ttl = RECONNECT_INTERVAL; 76 | 77 | apr_pool_create(&config->parse_pool, pool); 78 | 79 | return config; 80 | } 81 | 82 | static void *merge_gelf_configuration(apr_pool_t *p, void *parent, void *new) { 83 | return parent; 84 | } 85 | 86 | /* Registration function for extract functions */ 87 | void log_gelf_register_item(server_rec *server, apr_pool_t *p, 88 | char key, item_func *func, const char *arg, const char *field_name) { 89 | gelf_config *config = ap_get_module_config(server->module_config, &log_gelf_module); 90 | log_item *item; 91 | int i, length; 92 | 93 | if (!log_item_list) 94 | log_item_list = apr_array_make(p, 25, sizeof(log_item)); 95 | 96 | item = apr_array_push(log_item_list); 97 | item->key = key; 98 | item->extractor_func = func; 99 | item->field_name = field_name; 100 | if (arg) 101 | item->arg = arg; 102 | 103 | length = strlen(config->fields); 104 | for (i = 0; ifields, key)) != NULL) { 107 | config->parsed_fields[pos - config->fields] = item; 108 | } 109 | } 110 | } 111 | 112 | /* Enable GELF logging module */ 113 | static const char *set_gelf_enabled(cmd_parms *cmd, void *cfg, int flag) 114 | { 115 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 116 | config->enabled = flag; 117 | return NULL; 118 | } 119 | 120 | /* Sets basic connection info */ 121 | static const char *set_gelf_connection_parameter(cmd_parms *cmd, void *cfg, const char *arg) { 122 | apr_uri_t uri; 123 | apr_uri_parse(cmd->pool, arg, &uri); 124 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 125 | 126 | if (apr_strnatcmp(uri.scheme, "udp") == 0) { 127 | config->protocol = UDP; 128 | } 129 | 130 | if (apr_strnatcmp(uri.scheme, "tcp") == 0) { 131 | config->protocol = TCP; 132 | } 133 | 134 | if (config->protocol != UDP && config->protocol != TCP) { 135 | log_error(APLOG_MARK, APLOG_ERR, 0, cmd->server, 136 | "mod_log_gelf: Server protocol is %s, but must be 'udp://' or 'tcp://', disable module.", 137 | uri.scheme); 138 | config->enabled = 0; 139 | } 140 | 141 | if (uri.hostname) { 142 | config->server = uri.hostname; 143 | } 144 | 145 | if (uri.port) { 146 | config->port = uri.port; 147 | } 148 | 149 | return NULL; 150 | } 151 | 152 | /* Set additional tag field for server identification */ 153 | static const char *set_gelf_tag(cmd_parms *cmd, void *cfg, const char *arg) { 154 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 155 | config->tag = arg; 156 | return NULL; 157 | } 158 | 159 | /* Override source field */ 160 | static const char *set_gelf_source(cmd_parms *cmd, void *cfg, const char *arg) { 161 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 162 | config->source = arg; 163 | return NULL; 164 | } 165 | 166 | /* Override facilityfield */ 167 | static const char *set_gelf_facility(cmd_parms *cmd, void *cfg, const char *arg) { 168 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 169 | config->facility = arg; 170 | return NULL; 171 | } 172 | 173 | /* Override log format string */ 174 | static const char *set_gelf_fields(cmd_parms *cmd, void *cfg, const char *arg) { 175 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 176 | config->fields = arg; 177 | return NULL; 178 | } 179 | 180 | /* Override log format string */ 181 | static const char *set_gelf_cookie(cmd_parms *cmd, void *cfg, const char *arg) { 182 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 183 | config->cookie = arg; 184 | return NULL; 185 | } 186 | /* Override log format string */ 187 | static const char *set_gelf_header(cmd_parms *cmd, void *cfg, const char *arg) { 188 | gelf_config *config = ap_get_module_config(cmd->server->module_config, &log_gelf_module); 189 | config->header = arg; 190 | return NULL; 191 | } 192 | 193 | static const command_rec log_gelf_directives[] = { 194 | AP_INIT_FLAG("GelfEnabled", set_gelf_enabled, NULL, RSRC_CONF, "Enable or disable GELF logging"), 195 | AP_INIT_TAKE1("GelfUrl", set_gelf_connection_parameter, NULL, RSRC_CONF, "The URL to a Graylog server"), 196 | AP_INIT_TAKE1("GelfSource", set_gelf_source, NULL, RSRC_CONF, "Override source field"), 197 | AP_INIT_TAKE1("GelfFacility", set_gelf_facility, NULL, RSRC_CONF, "Override facility field"), 198 | AP_INIT_TAKE1("GelfTag", set_gelf_tag, NULL, RSRC_CONF, "Set a identification tag"), 199 | AP_INIT_TAKE1("GelfFields", set_gelf_fields, NULL, RSRC_CONF, "List of fields that should be logged"), 200 | AP_INIT_TAKE1("GelfCookie", set_gelf_cookie, NULL, RSRC_CONF, "Add this cookie the log message"), 201 | AP_INIT_TAKE1("GelfHeader", set_gelf_header, NULL, RSRC_CONF, "Add this header the log message"), 202 | { NULL } 203 | }; 204 | 205 | /* Connection pool */ 206 | static gelf_connection* log_gelf_connection_acquire(request_rec* r) { 207 | gelf_config* config = ap_get_module_config(r->server->module_config, &log_gelf_module) ; 208 | apr_status_t rv; 209 | gelf_connection *con; 210 | 211 | rv = apr_reslist_acquire(config->connection_pool, (void**)&con); 212 | if (rv != APR_SUCCESS || !con) { 213 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 214 | "mod_log_gelf: Failed to acquire GELF connection from pool: %s", 215 | apr_strerror(rv, errbuf, sizeof(errbuf))); 216 | return NULL; 217 | } 218 | 219 | return con; 220 | } 221 | 222 | static apr_status_t log_gelf_connection_release(request_rec* r, gelf_connection *con) { 223 | gelf_config* config = ap_get_module_config(r->server->module_config, &log_gelf_module); 224 | apr_status_t rv; 225 | rv = apr_reslist_release(config->connection_pool, con); 226 | if (rv != APR_SUCCESS) { 227 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_log_gelf: Can not release GELF socket."); 228 | } 229 | 230 | return rv; 231 | } 232 | 233 | static apr_status_t log_gelf_get_gelf_connection(gelf_connection *gc, gelf_config *config, apr_pool_t *pool) { 234 | apr_status_t rv; 235 | int proto = 0; 236 | int type = 0; 237 | 238 | if (config->protocol == TCP) { 239 | proto = APR_PROTO_TCP; 240 | type = SOCK_STREAM; 241 | } else if (config->protocol == UDP) { 242 | proto = APR_PROTO_UDP; 243 | type = SOCK_DGRAM; 244 | } 245 | 246 | if (verbose > 0) { 247 | ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, pool, 248 | "mod_log_gelf: Connecting to server: %s", config->server); 249 | } 250 | 251 | rv = apr_sockaddr_info_get(&gc->gelf_addr, config->server, APR_INET, config->port, 0, pool); 252 | if (rv != APR_SUCCESS) { 253 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 254 | "mod_log_gelf: Error setting GELF recipient %s:%d", config->server, config->port); 255 | return rv; 256 | } 257 | 258 | rv = apr_socket_create(&gc->gelf_sock, gc->gelf_addr->family, type, proto, pool); 259 | if (rv != APR_SUCCESS) { 260 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 261 | "mod_log_gelf: Error opening GELF socket: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 262 | return rv; 263 | } 264 | 265 | rv = apr_socket_connect(gc->gelf_sock, gc->gelf_addr); 266 | if (rv != APR_SUCCESS) { 267 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 268 | "mod_log_gelf: Error connecting to GELF port: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 269 | return rv; 270 | } 271 | 272 | /* set socket options */ 273 | rv = apr_socket_opt_set(gc->gelf_sock, APR_SO_SNDBUF, SEND_BUFFER); 274 | if (rv != APR_SUCCESS) { 275 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 276 | "mod_log_gelf: Error setting send buffer: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 277 | return rv; 278 | } 279 | 280 | if (config->protocol == TCP) { 281 | /* TCP socket options */ 282 | rv = apr_socket_opt_set(gc->gelf_sock, APR_SO_NONBLOCK, 0); 283 | if (rv != APR_SUCCESS) { 284 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 285 | "mod_log_gelf: Error setting socket to blocking: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 286 | return rv; 287 | } 288 | 289 | rv = apr_socket_opt_set(gc->gelf_sock, APR_TCP_NODELAY, 1); 290 | if (rv != APR_SUCCESS) { 291 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 292 | "mod_log_gelf: Error setting socket TCP nodelay: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 293 | return rv; 294 | } 295 | 296 | rv = apr_socket_timeout_set(gc->gelf_sock, 0); 297 | if (rv != APR_SUCCESS) { 298 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 299 | "mod_log_gelf: Error setting socket timeout: %s", apr_strerror(rv, errbuf, sizeof(errbuf))); 300 | return rv; 301 | } 302 | } 303 | 304 | return APR_SUCCESS; 305 | } 306 | 307 | static apr_status_t gelf_pool_construct(void** rs, void* params, apr_pool_t* pool) { 308 | gelf_config *config = (gelf_config*)params; 309 | apr_status_t rv; 310 | 311 | if (config->enabled < 1) { 312 | /* module disabled, no socket needed */ 313 | return APR_SUCCESS; 314 | } 315 | 316 | if (verbose > 0) { 317 | ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, pool, "mod_log_gelf: Creating new socket for pool: %s:%d", 318 | config->server, config->port); 319 | } 320 | 321 | gelf_connection *con; 322 | con = apr_palloc(pool, sizeof(gelf_connection)); 323 | rv = log_gelf_get_gelf_connection(con, config, pool); 324 | if (rv != APR_SUCCESS) { 325 | return APR_EGENERAL; 326 | } 327 | 328 | *rs = con; 329 | 330 | if (!*rs ) { 331 | ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, pool, "mod_log_gelf: Failed to store socket in resource list"); 332 | return APR_EGENERAL; 333 | } 334 | return APR_SUCCESS; 335 | } 336 | 337 | static apr_status_t gelf_pool_destruct(void* resource, void* params, apr_pool_t* pool) { 338 | if (resource) { 339 | if (verbose > 0) { 340 | ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, pool, "mod_log_gelf: Removing socket from pool"); 341 | } 342 | gelf_connection *con = (gelf_connection*)resource; 343 | apr_socket_close(con->gelf_sock); 344 | } 345 | return APR_SUCCESS ; 346 | } 347 | 348 | static apr_status_t log_gelf_child_exit(void *resource) { 349 | apr_reslist_t *connection_pool = resource; 350 | apr_pool_t *pool = NULL; 351 | apr_pool_create(&pool, NULL); 352 | 353 | 354 | while (apr_reslist_acquired_count(connection_pool) != 0) { 355 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 356 | "mod_log_gelf: Socket pool not empty: %i", apr_reslist_acquired_count(connection_pool)); 357 | } 358 | apr_reslist_destroy(connection_pool); 359 | } 360 | 361 | /* Registered hooks */ 362 | static int log_gelf_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *server) { 363 | gelf_config *config = ap_get_module_config(server->module_config, &log_gelf_module); 364 | 365 | /* initialize resource list to keep track of socket pool */ 366 | if ( apr_reslist_create(&config->connection_pool, 367 | config->nmin, 368 | config->nkeep, 369 | config->nmax, 370 | config->ttl, 371 | gelf_pool_construct, 372 | gelf_pool_destruct, 373 | config, p) != APR_SUCCESS ) { 374 | ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server, "mod_log_gelf: Failed to initialize resource pool, disabling GELF logging."); 375 | config->enabled = 0; 376 | return OK; 377 | } 378 | // apr_pool_cleanup_register(p, config->connection_pool, 379 | // (void*)apr_reslist_destroy, apr_pool_cleanup_null); 380 | apr_pool_cleanup_register(p, config->connection_pool, 381 | log_gelf_child_exit, log_gelf_child_exit); 382 | 383 | /* allocate memory for log items */ 384 | config->parsed_fields = apr_pcalloc(config->parse_pool, strlen(config->fields) * sizeof(log_item *)); 385 | 386 | /* register available logging fields */ 387 | log_gelf_register_item(server,p,'A', extract_agent, NULL, "_agent"); 388 | log_gelf_register_item(server,p,'a', extract_request_query, NULL, "_request_args"); 389 | log_gelf_register_item(server,p,'B', extract_bytes_sent, NULL, "_bytes_sent"); 390 | log_gelf_register_item(server,p,'C', extract_connection_status, NULL, "_connection_status"); 391 | log_gelf_register_item(server,p,'c', extract_specific_cookie, config->cookie, "_cookie"); 392 | log_gelf_register_item(server,p,'D', extract_request_duration, NULL, "_request_duration_us"); 393 | log_gelf_register_item(server,p,'f', extract_request_file, NULL, "_request_file"); 394 | log_gelf_register_item(server,p,'H', extract_request_protocol, NULL, "_request_protocol"); 395 | log_gelf_register_item(server,p,'h', extract_remote_host, NULL, "_remote_host"); 396 | log_gelf_register_item(server,p,'i', extract_remote_address, NULL, "_remote_address"); 397 | log_gelf_register_item(server,p,'L', extract_local_address, NULL, "_local_address"); 398 | log_gelf_register_item(server,p,'l', extract_remote_logname, NULL, "_remote_login_name"); 399 | log_gelf_register_item(server,p,'m', extract_request_method, NULL, "_request_method"); 400 | log_gelf_register_item(server,p,'p', extract_server_port, NULL, "_server_port"); 401 | log_gelf_register_item(server,p,'P', extract_child_pid, NULL, "_child_pid"); 402 | log_gelf_register_item(server,p,'R', extract_referer, NULL, "_referer"); 403 | log_gelf_register_item(server,p,'r', extract_request_line, NULL, "_request_line"); 404 | log_gelf_register_item(server,p,'s', extract_status, NULL, "_status"); 405 | log_gelf_register_item(server,p,'t', extract_request_time, NULL, "_request_time"); 406 | log_gelf_register_item(server,p,'U', extract_request_uri, NULL, "_request_uri"); 407 | log_gelf_register_item(server,p,'u', extract_remote_user, NULL, "_remote_user"); 408 | log_gelf_register_item(server,p,'V', extract_server_name, NULL, "_server_name"); 409 | log_gelf_register_item(server,p,'v', extract_virtual_host, NULL, "_virtual_host"); 410 | log_gelf_register_item(server,p,'X', extract_header, config->header, "_header"); 411 | 412 | return OK; 413 | } 414 | 415 | static int log_gelf_transaction(request_rec *request) { 416 | transferData* tdata; 417 | gelf_config *config = ap_get_module_config(request->server->module_config, &log_gelf_module); 418 | 419 | /* skip logging if module is disabled or no connection parameters are given */ 420 | if (config->enabled == 0) { 421 | log_error(APLOG_MARK, APLOG_NOTICE, 0, request->server, 422 | "mod_log_gelf: Module is disbaled, not sending log data!"); 423 | return OK; 424 | } 425 | 426 | /* skip logging if endpoint is not configured properly */ 427 | if (!config->server || !config->port) { 428 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 429 | "mod_log_gelf: 'GelfUrl' not set in server configuration. Use the format 'protocol://ip:port'"); 430 | return OK; 431 | } 432 | 433 | char* json = log_gelf_make_json(request); 434 | 435 | if (config->protocol == TCP) { 436 | /* allocate memory for actual log message */ 437 | tdata = apr_palloc(request->pool, sizeof(transferData)); 438 | memset(tdata, '\0', sizeof(transferData)); 439 | tdata->data = json; 440 | tdata->size = strlen(json); 441 | log_gelf_send_message_tcp(tdata, request); 442 | } else if (config->protocol == UDP) { 443 | tdata = log_gelf_zlib_compress(json, request); 444 | log_gelf_send_message_udp(tdata, request); 445 | } 446 | 447 | return OK; 448 | } 449 | 450 | char * log_gelf_make_json(request_rec *request) { 451 | int i, length; 452 | gelf_config *config = ap_get_module_config(request->server->module_config, &log_gelf_module); 453 | 454 | /* init json object */ 455 | json_object* object = json_object_new_object(); 456 | 457 | /* attach field pairs to json root */ 458 | json_add_string(object, "version", "1.1"); 459 | json_add_string(object, "host", config->source); 460 | // json_add_string(object, "short_message", extract_request_line(request, NULL)); 461 | json_object_object_add(object, "short_message", extract_request_line(request, NULL)); 462 | json_add_string(object, "facility", config->facility); 463 | json_add_int(object, "level", 6); /*0=Emerg, 1=Alert, 2=Crit, 3=Error, 4=Warn, 5=Notice, 6=Info */ 464 | json_add_double(object, "timestamp", log_gelf_get_timestamp()); 465 | 466 | /* add extra fields */ 467 | length = strlen(config->fields); 468 | for (i = 0; iparsed_fields[i]; 470 | json_object* fval; 471 | if (item != NULL) { 472 | fval = item->extractor_func(request, (char*)item->arg); 473 | if (fval != NULL) { 474 | json_object_object_add(object, item->field_name, fval); 475 | } 476 | 477 | } 478 | } 479 | 480 | /* add conditional fields */ 481 | if (config->tag) { 482 | json_add_string(object, "_tag", config->tag); 483 | } 484 | 485 | /* get json string */ 486 | const char * json_str = json_object_to_json_string_ext(object, JSON_C_TO_STRING_PLAIN); 487 | char *result = apr_pcalloc(request->pool, strlen(json_str)+1); 488 | apr_cpystrn(result, json_str, strlen(json_str)+1); 489 | 490 | /* free temporary json object */ 491 | json_object_put(object); 492 | 493 | return result; 494 | } 495 | 496 | json_object * json_add_string(json_object *jobj, const char *key, const char *value) { 497 | json_object *jstring = json_object_new_string(value); 498 | json_object_object_add(jobj, key, jstring); 499 | 500 | return jobj; 501 | } 502 | 503 | json_object * json_add_int(json_object *jobj, const char *key, int value) { 504 | json_object *jint = json_object_new_int(value); 505 | json_object_object_add(jobj, key, jint); 506 | 507 | return jobj; 508 | } 509 | 510 | json_object * json_add_double(json_object *jobj, const char *key, double value) { 511 | json_object *jdouble = json_object_new_double(value); 512 | json_object_object_add(jobj, key, jdouble); 513 | 514 | return jobj; 515 | } 516 | 517 | transferData* log_gelf_zlib_compress(const char *line, request_rec *request) { 518 | /* init stream struc and buffers */ 519 | z_stream* strm; 520 | size_t len = strlen(line); 521 | void * buf = apr_palloc(request->pool, len); 522 | 523 | strm = apr_palloc(request->pool, sizeof(z_stream)); 524 | memset(strm, '\0', sizeof(z_stream)); 525 | strm->zalloc = Z_NULL; 526 | strm->zfree = Z_NULL; 527 | strm->opaque = Z_NULL; 528 | strm->data_type = Z_TEXT; 529 | 530 | /* deflate log message */ 531 | if (deflateInit(strm, 6) != Z_OK) { 532 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 533 | "mod_log_gelf: Error initialising zlib deflate"); 534 | } 535 | strm->avail_in = len; 536 | strm->next_in = (void *)line; 537 | strm->next_out = buf; 538 | strm->avail_out = len; 539 | if ( deflate(strm, Z_FINISH) == Z_STREAM_ERROR) { 540 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 541 | "mod_log_gelf: Error compressing with zlib deflate"); 542 | } 543 | int csize = len - strm->avail_out; 544 | if (verbose > 0) { 545 | log_error(APLOG_MARK, APLOG_NOTICE, 0, request->server, 546 | "mod_log_gelf: Json length: %i, compressed: %i", (int)len, csize); 547 | } 548 | deflateEnd(strm); 549 | 550 | /* create data to transfer */ 551 | transferData * ret = apr_palloc(request->pool, sizeof(transferData)); 552 | memset(ret, '\0', sizeof(transferData)); 553 | ret->data = buf; 554 | ret->size = csize; 555 | 556 | return ret; 557 | } 558 | 559 | void log_gelf_send_message_udp(const transferData* payload, request_rec *request) { 560 | apr_status_t rv; 561 | apr_size_t len; 562 | gelf_config *config = ap_get_module_config(request->server->module_config, &log_gelf_module); 563 | 564 | if (payload->size > 0) { 565 | len = payload->size; 566 | } else { 567 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 568 | "mod_log_gelf: Got empty log message, not sending anything."); 569 | return; 570 | } 571 | 572 | /* acquire a free socket, send message, release socket */ 573 | gelf_connection *con = log_gelf_connection_acquire(request); 574 | if (!con) { 575 | return; 576 | } 577 | 578 | if (verbose > 0) { 579 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 580 | "mod_log_gelf: Sending GELF message: %s", (char*)payload->data); 581 | } 582 | 583 | rv = apr_socket_send(con->gelf_sock, payload->data, &len); 584 | if (rv != APR_SUCCESS) { 585 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 586 | "mod_log_gelf: Error writing to socket %d bytes. Error %s", 587 | payload->size, apr_strerror(rv, errbuf, sizeof(errbuf))); 588 | } 589 | 590 | log_gelf_connection_release(request, con); 591 | } 592 | 593 | void log_gelf_send_message_tcp(const transferData* payload, request_rec *request) { 594 | apr_status_t rv; 595 | apr_size_t len; 596 | gelf_config *config = ap_get_module_config(request->server->module_config, &log_gelf_module); 597 | 598 | if (payload->size > 0) { 599 | /* one extra byte for string termination */ 600 | len = payload->size + 1; 601 | } else { 602 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 603 | "mod_log_gelf: Got empty log message, not sending anything."); 604 | return; 605 | } 606 | 607 | /* copy payload and append '\0' */ 608 | const char* gelf_payload = apr_pstrmemdup(request->pool, payload->data, payload->size); 609 | 610 | /* acquire a free socket, send message, release socket */ 611 | gelf_connection *con = log_gelf_connection_acquire(request); 612 | if (!con || !con->gelf_sock) { 613 | return; 614 | } 615 | 616 | if (verbose > 0) { 617 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 618 | "mod_log_gelf: Sending GELF message: %s", gelf_payload); 619 | } 620 | 621 | rv = apr_socket_send(con->gelf_sock, gelf_payload, &len); 622 | if (rv != APR_SUCCESS) { 623 | log_error(APLOG_MARK, APLOG_ERR, 0, request->server, 624 | "mod_log_gelf: Error writing to socket %d bytes. Error %s", 625 | payload->size, apr_strerror(rv, errbuf, sizeof(errbuf))); 626 | apr_reslist_invalidate(config->connection_pool, con) ; 627 | } else { 628 | log_gelf_connection_release(request, con); 629 | } 630 | } 631 | 632 | double log_gelf_get_timestamp() { 633 | return ((double) (apr_time_now() / 1000)) / 1000.0; 634 | } 635 | 636 | static void log_gelf_sig_pipe(int sig){ 637 | apr_pool_t *pool = NULL; 638 | apr_pool_create(&pool, NULL); 639 | 640 | if (verbose > 0) { 641 | ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, 642 | "mod_log_gelf: Got signal SIGPIPE. Most likely GELF server went away."); 643 | } 644 | } 645 | 646 | static void log_gelf_child_init(apr_pool_t *p, server_rec *server) { 647 | apr_status_t rv; 648 | gelf_config *config = ap_get_module_config(server->module_config, &log_gelf_module); 649 | 650 | if (verbose > 0) { 651 | log_error(APLOG_MARK, APLOG_NOTICE, 0, server, 652 | "mod_log_gelf: Initializing new child process."); 653 | } 654 | 655 | if (config->server == NULL) { 656 | log_error(APLOG_MARK, APLOG_NOTICE, 0, server, 657 | "mod_log_gelf: Creating new configuration."); 658 | gelf_config *config = create_gelf_configuration(p, server); 659 | ap_set_module_config(server->module_config, &log_gelf_module, config); 660 | } 661 | 662 | apr_signal(SIGPIPE, log_gelf_sig_pipe); 663 | } 664 | 665 | static void register_hooks(apr_pool_t *p) { 666 | ap_hook_post_config(log_gelf_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); 667 | ap_hook_child_init(log_gelf_child_init, NULL, NULL, APR_HOOK_MIDDLE); 668 | ap_hook_log_transaction(log_gelf_transaction, NULL, NULL, APR_HOOK_LAST); 669 | } 670 | 671 | module AP_MODULE_DECLARE_DATA log_gelf_module = { 672 | STANDARD20_MODULE_STUFF, 673 | NULL, /* Per-directory configuration handler */ 674 | NULL, /* Merge handler for per-directory configurations */ 675 | create_gelf_configuration, /* Per-server configuration handler */ 676 | merge_gelf_configuration, /* Merge handler for per-server configurations */ 677 | log_gelf_directives, /* Any directives we may have for httpd */ 678 | register_hooks /* Our hook registering function */ 679 | }; 680 | -------------------------------------------------------------------------------- /src/mod_log_gelf.h: -------------------------------------------------------------------------------- 1 | typedef json_object* item_func(request_rec *r, char *a); 2 | typedef struct transferDataS { 3 | void * data; 4 | int size; 5 | } transferData; 6 | transferData* log_gelf_zlib_compress(const char* line, request_rec *request); 7 | char* log_gelf_make_json(request_rec *request); 8 | json_object* json_add_string(json_object *jobj, const char *key, const char *value); 9 | json_object* json_add_int(json_object *jobj, const char *key, int value); 10 | json_object* json_add_double(json_object *jobj, const char *key, double value); 11 | 12 | void log_gelf_register_item(server_rec *s, apr_pool_t *p, 13 | char key, item_func *func, const char *arg, const char *field_name); 14 | void log_gelf_send_message_udp(const transferData* payload, request_rec *request); 15 | void log_gelf_send_message_tcp(const transferData* payload, request_rec *request); 16 | 17 | double log_gelf_get_timestamp(); 18 | -------------------------------------------------------------------------------- /src/modules.mk: -------------------------------------------------------------------------------- 1 | mod_log_gelf.la: mod_log_gelf.slo 2 | $(SH_LINK) $(LIBS) -rpath $(libexecdir) -module -avoid-version mod_log_gelf.lo 3 | DISTCLEAN_TARGETS = modules.mk 4 | shared = mod_log_gelf.la 5 | --------------------------------------------------------------------------------