Browse Source

Initial import

master
Daniel Berteaud 2 months ago
parent
commit
9035410106
3 changed files with 151 additions and 0 deletions
  1. 114
    0
      systemd-journal-gelf
  2. 22
    0
      systemd-journal-gelf.service
  3. 15
    0
      systemd-journal-gelf.yml

+ 114
- 0
systemd-journal-gelf View File

@@ -0,0 +1,114 @@
1
+#!/usr/bin/perl
2
+
3
+use warnings;
4
+use strict;
5
+use JSON;
6
+use LWP::UserAgent;
7
+use Encode qw(encode);
8
+use Data::Dumper;
9
+use Compress::Zlib;
10
+use Getopt::Long;
11
+use Config::Any;
12
+
13
+my $config = '/etc/systemd/journal-gelf.yml';
14
+my $conf = {};
15
+
16
+if (-e $config) { 
17
+  print "Reading config file $config\n";
18
+  $conf = Config::Any->load_files( { files => [ $config ], flatten_to_hash => 1, use_ext => 1 } )->{$config};
19
+}
20
+
21
+GetOptions (
22
+  'state=s'    => \$conf->{state},
23
+  'compress!'  => \$conf->{compress},
24
+  'url=s'      => \$conf->{url},
25
+  'username=s' => \$conf->{username},
26
+  'password=s' => \$conf->{password}
27
+);
28
+
29
+$conf->{state}    //= '/var/lib/systemd-journal-gelf/state';
30
+$conf->{compress} //= 1;
31
+
32
+if (
33
+     not $conf->{url} or
34
+     ($conf->{username} and not $conf->{password}) or
35
+     (not $conf->{username} and $conf->{password})
36
+  ){
37
+  help();
38
+  die;
39
+}
40
+
41
+print "Starting the Systemd Journal GELF uploader daemon\n";
42
+
43
+my $ua = LWP::UserAgent->new(
44
+  # env_proxy => 1,
45
+  keep_alive => 1
46
+);
47
+$ua->default_header( 'Content-Type' => 'application/json' );
48
+if ( $conf->{compress} ){
49
+  $ua->default_header( 'Accept-Encoding' => HTTP::Message::decodable );
50
+  $ua->default_header( 'Content-Encoding' => 'gzip' );
51
+}
52
+
53
+# Check if the state file exists and contains a valid cursor
54
+my $cursor_arg = '';
55
+open CURSOR, "+<", $conf->{state};
56
+if (-e $conf->{state}){
57
+  my $cursor = <CURSOR>;
58
+  close CURSOR;
59
+  if ($cursor and $cursor =~ m/^s=[a-z\d]+;i=[a-z\d]+;b=[a-z\d]+;m=[a-z\d]+;t=[a-z\d]+;x=[a-z\d]+$/){
60
+    print "Valid cursor found in " . $conf->{state} . ", will start back from here\n";
61
+    $cursor_arg = " --after-cursor='" . $cursor . "'";
62
+  } else {
63
+    print $conf->{state} . " contains an invalid cursor, so we're wiping it\n";
64
+    unlink $conf->{state};
65
+  }
66
+}
67
+
68
+open JOURNAL, "journalctl -f -o json$cursor_arg |";
69
+while (my $entry = <JOURNAL>){
70
+  my $msg = from_json($entry);
71
+  my $gelf = {
72
+    version => 1.1,
73
+    short_message => $msg->{MESSAGE},
74
+    host => $msg->{_HOSTNAME},
75
+    timestamp => int ($msg->{__REALTIME_TIMESTAMP} / (1000 * 1000)),
76
+    level => $msg->{PRIORITY}
77
+  };
78
+  foreach (grep !/^MESSAGE|_HOSTNAME|__REALTIME_TIMESTAMP|PRIORITY$/,  keys %$msg){
79
+    my $key = lc (($_ =~ m/^_/) ? $_ : '_' . $_);
80
+    $gelf->{$key} = $msg->{$_};
81
+  }
82
+  my $retry = 0;
83
+  my $resp;
84
+  do {
85
+    if ($retry > 0){
86
+      print "Sending message to " . $conf->{url} . " failed : got code " .
87
+        $resp->code . " (" . $resp->message . "). Tring again in $retry seconds\n";
88
+      sleep $retry;
89
+    }
90
+    $resp = $ua->post($conf->{url}, Content => Compress::Zlib::memGzip(encode('utf-8', to_json($gelf))));
91
+    $retry = ($retry > 0) ? $retry * 2 : 1;
92
+  } while ($resp->code != 202 and $retry < 600);
93
+  if ($resp->code == 202){
94
+    open CURSOR, ">", $conf->{state};
95
+    print CURSOR $msg->{__CURSOR};
96
+    close CURSOR
97
+  } else {
98
+    die "Error sending data to GELF server\n";
99
+  }
100
+}
101
+
102
+sub help {
103
+  print <<"_EOF"
104
+
105
+Usage: $0 --url=<URL> [--compress|--no-compress] [--user=production --password=secr3t] [--state=/path/to/file]
106
+
107
+  * --url is the http or https URL where you will push your gelf formated logs. This is mandatory
108
+  * --compress or --no-compress : will turn on or off gzip compression of logs. Default is on, but can be usefull to disable for debugging
109
+  * --username and --password may be used if URL is protected with a basic auth mecanism. Either both or none must be provided
110
+  * --state can be used to specify where to record the last correctly sent message, so we can start from here when
111
+    systemd-journal-gelf is restarted or if there's a network problem. Default value is /var/lib/systemd-journal-gelf/state
112
+
113
+_EOF
114
+}

+ 22
- 0
systemd-journal-gelf.service View File

@@ -0,0 +1,22 @@
1
+[Unit]
2
+Description=Systemd Journal GEL HTTP uploader
3
+
4
+[Service]
5
+Type=simple
6
+User=systemd-journal-gelf
7
+Group=adm
8
+ExecStart=/usr/local/bin/systemd-journal-gelf -c /etc/systemd/journal-gelf.yml
9
+PrivateTmp=yes
10
+PrivateDevices=yes
11
+ProtectSystem=full
12
+ProtectHome=yes
13
+NoNewPrivileges=yes
14
+MemoryLimit=200M
15
+SyslogIdentifier=systemd-journal-gelf
16
+Restart=on-failure
17
+StartLimitInterval=0
18
+RestartSec=30
19
+
20
+[Install]
21
+WantedBy=multi-user.target
22
+

+ 15
- 0
systemd-journal-gelf.yml View File

@@ -0,0 +1,15 @@
1
+---
2
+# Mandatory setting : URL on which gelf formated message will be posted
3
+# url: https://graylog.domain.net:12201/gelf
4
+
5
+# Path to a file where we save the last correctly sent message, so we can start
6
+# back where we left
7
+# state: /var/lib/systemd-journal-gelf
8
+
9
+# If enabled, data will be compressed before being sent. Default is enabled
10
+# Might be disabled for debuging purpose
11
+# compress: True
12
+
13
+# If you http endpoint is protected with basic auth, set username and password
14
+# username: foo
15
+# password: p@ssw0rd

Loading…
Cancel
Save