1 /*
2 * MOC - music on console
3 * Copyright (C) 2004 Damian Pietras <daper@daper.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 */
11
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include <stdio.h>
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <pthread.h>
20 #include <sys/time.h>
21 #include <time.h>
22 #include <errno.h>
23 #include <signal.h>
24
25 #include "common.h"
26 #include "lists.h"
27 #include "log.h"
28
29 static FILE *logfp = NULL; /* logging file stream */
30 static enum {
31 UNINITIALISED,
32 BUFFERING,
33 LOGGING
34 } logging_state = UNINITIALISED;
35 static lists_t_strs *buffered_log = NULL;
36 static int log_records_spilt = 0;
37
38 static pthread_mutex_t logging_mutex = PTHREAD_MUTEX_INITIALIZER;
39
40 #ifndef NDEBUG
41 static struct {
42 int sig;
43 const char *name;
44 volatile uint64_t raised;
45 uint64_t logged;
46 } sig_info[] = {
47 {SIGINT, "SIGINT", 0, 0},
48 {SIGHUP, "SIGHUP", 0, 0},
49 {SIGQUIT, "SIGQUIT", 0, 0},
50 {SIGTERM, "SIGTERM", 0, 0},
51 {SIGCHLD, "SIGCHLD", 0, 0},
52 {SIGWINCH, "SIGWINCH", 0, 0},
53 {0, "SIG other", 0, 0}
54 };
55 #endif
56
57 #ifndef NDEBUG
log_signal(int sig)58 void log_signal (int sig)
59 {
60 int ix = 0;
61
62 while (sig_info[ix].sig && sig_info[ix].sig != sig)
63 ix += 1;
64
65 sig_info[ix].raised += 1;
66 }
67 #endif
68
flush_log(void)69 static inline void flush_log (void)
70 {
71 int rc;
72
73 if (logfp) {
74 do {
75 rc = fflush (logfp);
76 } while (rc != 0 && errno == EINTR);
77 }
78 }
79
locked_logit(const char * file,const int line,const char * function,const char * msg)80 static void locked_logit (const char *file, const int line,
81 const char *function, const char *msg)
82 {
83 char time_str[20];
84 struct timeval utc_time;
85 time_t tv_sec;
86 struct tm tm_time;
87 const char fmt[] = "%s.%06u: %s:%d %s(): %s\n";
88
89 gettimeofday (&utc_time, NULL);
90 tv_sec = utc_time.tv_sec;
91 localtime_r (&tv_sec, &tm_time);
92 strftime (time_str, sizeof (time_str), "%b %e %T", &tm_time);
93
94 if (logfp) {
95 fprintf (logfp, fmt, time_str, (unsigned)utc_time.tv_usec,
96 file, line, function, msg);
97 }
98 else if (logging_state == BUFFERING) {
99 int len;
100 char *str;
101
102 len = snprintf (NULL, 0, fmt, time_str, (unsigned)utc_time.tv_usec,
103 file, line, function, msg);
104 str = xmalloc (len + 1);
105 snprintf (str, len + 1, fmt, time_str, (unsigned)utc_time.tv_usec,
106 file, line, function, msg);
107
108 lists_strs_push (buffered_log, str);
109 }
110 }
111
log_signals_raised(void)112 static void log_signals_raised (void)
113 {
114 #ifndef NDEBUG
115 size_t ix;
116
117 for (ix = 0; ix < ARRAY_SIZE(sig_info); ix += 1) {
118 while (sig_info[ix].raised > sig_info[ix].logged) {
119 locked_logit (__FILE__, __LINE__, __FUNCTION__, sig_info[ix].name);
120 sig_info[ix].logged += 1;
121 }
122 }
123 #endif
124 }
125
126 /* Put something into the log */
internal_logit(const char * file,const int line,const char * function,const char * format,...)127 void internal_logit (const char *file, const int line, const char *function,
128 const char *format, ...)
129 {
130 char *msg;
131 va_list va;
132
133 LOCK(logging_mutex);
134
135 if (!logfp) {
136 switch (logging_state) {
137 case UNINITIALISED:
138 buffered_log = lists_strs_new (128);
139 logging_state = BUFFERING;
140 break;
141 case BUFFERING:
142 /* Don't let storage run away on us. */
143 if (lists_strs_size (buffered_log) < lists_strs_capacity (buffered_log))
144 break;
145 log_records_spilt += 1;
146 case LOGGING:
147 goto end;
148 }
149 }
150
151 log_signals_raised ();
152
153 va_start (va, format);
154 msg = format_msg_va (format, va);
155 va_end (va);
156 locked_logit (file, line, function, msg);
157 free (msg);
158
159 flush_log ();
160
161 log_signals_raised ();
162
163 end:
164 UNLOCK(logging_mutex);
165 }
166
167 /* fake logit() function for NDEBUG */
fake_logit(const char * format ATTR_UNUSED,...)168 void fake_logit (const char *format ATTR_UNUSED, ...)
169 {
170 }
171
172 /* Initialize logging stream */
log_init_stream(FILE * f,const char * fn)173 void log_init_stream (FILE *f, const char *fn)
174 {
175 char *msg;
176
177 LOCK(logging_mutex);
178
179 logfp = f;
180
181 if (logging_state == BUFFERING) {
182 if (logfp) {
183 int ix;
184
185 for (ix = 0; ix < lists_strs_size (buffered_log); ix += 1)
186 fprintf (logfp, "%s", lists_strs_at (buffered_log, ix));
187 }
188 lists_strs_free (buffered_log);
189 buffered_log = NULL;
190 }
191
192 logging_state = LOGGING;
193
194 msg = format_msg ("Writing log to: %s", fn);
195 locked_logit (__FILE__, __LINE__, __FUNCTION__, msg);
196 free (msg);
197
198 if (log_records_spilt > 0) {
199 msg = format_msg ("%d log records spilt", log_records_spilt);
200 locked_logit (__FILE__, __LINE__, __FUNCTION__, msg);
201 free (msg);
202 }
203
204 flush_log ();
205
206 UNLOCK(logging_mutex);
207 }
208
log_close()209 void log_close ()
210 {
211 LOCK(logging_mutex);
212 if (!(logfp == stdout || logfp == stderr || logfp == NULL)) {
213 fclose (logfp);
214 logfp = NULL;
215 }
216 if (buffered_log) {
217 lists_strs_free (buffered_log);
218 buffered_log = NULL;
219 }
220 log_records_spilt = 0;
221 UNLOCK(logging_mutex);
222 }
223