/src/MapServer/src/mapdebug.c
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: Implementation of debug/logging, msDebug() and related functions. |
6 | | * Author: Daniel Morissette |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 1996-2007 Regents of the University of Minnesota. |
10 | | * |
11 | | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | | * copy of this software and associated documentation files (the "Software"), |
13 | | * to deal in the Software without restriction, including without limitation |
14 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
15 | | * and/or sell copies of the Software, and to permit persons to whom the |
16 | | * Software is furnished to do so, subject to the following conditions: |
17 | | * |
18 | | * The above copyright notice and this permission notice shall be included in |
19 | | * all copies of this Software or works derived from this Software. |
20 | | * |
21 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
22 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
24 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
26 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
27 | | * DEALINGS IN THE SOFTWARE. |
28 | | ****************************************************************************/ |
29 | | |
30 | | #include "mapserver.h" |
31 | | #include "maperror.h" |
32 | | #include "mapthread.h" |
33 | | #include "maptime.h" |
34 | | |
35 | | #include "cpl_conv.h" |
36 | | |
37 | | #include <time.h> |
38 | | #ifndef _WIN32 |
39 | | #include <sys/time.h> |
40 | | #include <unistd.h> |
41 | | #endif |
42 | | #include <stdarg.h> |
43 | | |
44 | | #ifdef NEED_NONBLOCKING_STDERR |
45 | | #include <fcntl.h> |
46 | | #endif |
47 | | |
48 | | #ifdef _WIN32 |
49 | | #include <windows.h> /* OutputDebugStringA() */ |
50 | | #endif |
51 | | |
52 | | #ifndef USE_THREAD |
53 | | |
54 | 84.6k | debugInfoObj *msGetDebugInfoObj() { |
55 | 84.6k | static debugInfoObj debuginfo = { |
56 | 84.6k | MS_DEBUGLEVEL_ERRORSONLY, MS_DEBUGMODE_OFF, NULL, NULL, 0, NULL}; |
57 | 84.6k | return &debuginfo; |
58 | 84.6k | } |
59 | | |
60 | | #else |
61 | | |
62 | | static debugInfoObj *debuginfo_list = NULL; |
63 | | |
64 | | debugInfoObj *msGetDebugInfoObj() { |
65 | | debugInfoObj *link; |
66 | | void *thread_id; |
67 | | debugInfoObj *ret_obj; |
68 | | |
69 | | msAcquireLock(TLOCK_DEBUGOBJ); |
70 | | |
71 | | thread_id = msGetThreadId(); |
72 | | |
73 | | /* find link for this thread */ |
74 | | |
75 | | for (link = debuginfo_list; |
76 | | link != NULL && link->thread_id != thread_id && link->next != NULL && |
77 | | link->next->thread_id != thread_id; |
78 | | link = link->next) { |
79 | | } |
80 | | |
81 | | /* If the target thread link is already at the head of the list were ok */ |
82 | | if (debuginfo_list != NULL && debuginfo_list->thread_id == thread_id) { |
83 | | } |
84 | | |
85 | | /* We don't have one ... initialize one. */ |
86 | | else if (link == NULL || link->next == NULL) { |
87 | | debugInfoObj *new_link; |
88 | | |
89 | | new_link = (debugInfoObj *)msSmallMalloc(sizeof(debugInfoObj)); |
90 | | new_link->next = debuginfo_list; |
91 | | new_link->thread_id = thread_id; |
92 | | new_link->global_debug_level = MS_DEBUGLEVEL_ERRORSONLY; |
93 | | new_link->debug_mode = MS_DEBUGMODE_OFF; |
94 | | new_link->errorfile = NULL; |
95 | | new_link->fp = NULL; |
96 | | debuginfo_list = new_link; |
97 | | } |
98 | | |
99 | | /* If the link is not already at the head of the list, promote it */ |
100 | | else { |
101 | | debugInfoObj *target = link->next; |
102 | | |
103 | | link->next = link->next->next; |
104 | | target->next = debuginfo_list; |
105 | | debuginfo_list = target; |
106 | | } |
107 | | |
108 | | ret_obj = debuginfo_list; |
109 | | |
110 | | msReleaseLock(TLOCK_DEBUGOBJ); |
111 | | |
112 | | return ret_obj; |
113 | | } |
114 | | #endif |
115 | | |
116 | | /* msSetErrorFile() |
117 | | ** |
118 | | ** Set output target, ready to write to it, open file if necessary |
119 | | ** |
120 | | ** If pszRelToPath != NULL then we will try to make the value relative to |
121 | | ** this path if it is not absolute already and it's not one of the special |
122 | | ** values (stderr, stdout, windowsdebug) |
123 | | ** |
124 | | ** Returns MS_SUCCESS/MS_FAILURE |
125 | | */ |
126 | 340 | int msSetErrorFile(const char *pszErrorFile, const char *pszRelToPath) { |
127 | 340 | char extended_path[MS_MAXPATHLEN]; |
128 | 340 | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
129 | | |
130 | 340 | if (strcmp(pszErrorFile, "stderr") != 0 && |
131 | 340 | strcmp(pszErrorFile, "stdout") != 0 && |
132 | 340 | strcmp(pszErrorFile, "windowsdebug") != 0) { |
133 | | /* Try to make the path relative */ |
134 | 223 | if (msBuildPath(extended_path, pszRelToPath, pszErrorFile) == NULL) |
135 | 1 | return MS_FAILURE; |
136 | 222 | pszErrorFile = extended_path; |
137 | 222 | } |
138 | | |
139 | 339 | if (debuginfo->errorfile && pszErrorFile && |
140 | 339 | strcmp(debuginfo->errorfile, pszErrorFile) == 0) { |
141 | | /* Nothing to do, already writing to the right place */ |
142 | 170 | return MS_SUCCESS; |
143 | 170 | } |
144 | | |
145 | | /* Close current output file if any */ |
146 | 169 | msCloseErrorFile(); |
147 | | |
148 | | /* NULL or empty target will just close current output and return */ |
149 | 169 | if (pszErrorFile == NULL || *pszErrorFile == '\0') |
150 | 0 | return MS_SUCCESS; |
151 | | |
152 | 169 | if (strcmp(pszErrorFile, "stderr") == 0) { |
153 | 44 | debuginfo->fp = stderr; |
154 | 44 | debuginfo->errorfile = msStrdup(pszErrorFile); |
155 | 44 | debuginfo->debug_mode = MS_DEBUGMODE_STDERR; |
156 | 125 | } else if (strcmp(pszErrorFile, "stdout") == 0) { |
157 | 27 | debuginfo->fp = stdout; |
158 | 27 | debuginfo->errorfile = msStrdup(pszErrorFile); |
159 | 27 | debuginfo->debug_mode = MS_DEBUGMODE_STDOUT; |
160 | 98 | } else if (strcmp(pszErrorFile, "windowsdebug") == 0) { |
161 | | #ifdef _WIN32 |
162 | | debuginfo->errorfile = msStrdup(pszErrorFile); |
163 | | debuginfo->fp = NULL; |
164 | | debuginfo->debug_mode = MS_DEBUGMODE_WINDOWSDEBUG; |
165 | | #else |
166 | 0 | msSetError( |
167 | 0 | MS_MISCERR, |
168 | 0 | "'MS_ERRORFILE windowsdebug' is available only on Windows platforms.", |
169 | 0 | "msSetErrorFile()"); |
170 | 0 | return MS_FAILURE; |
171 | 0 | #endif |
172 | 98 | } else { |
173 | 98 | debuginfo->fp = fopen(pszErrorFile, "a"); |
174 | 98 | if (debuginfo->fp == NULL) { |
175 | 3 | msSetError(MS_MISCERR, "Failed to open MS_ERRORFILE %s", |
176 | 3 | "msSetErrorFile()", pszErrorFile); |
177 | 3 | return MS_FAILURE; |
178 | 3 | } |
179 | 95 | debuginfo->errorfile = msStrdup(pszErrorFile); |
180 | 95 | debuginfo->debug_mode = MS_DEBUGMODE_FILE; |
181 | 95 | } |
182 | | |
183 | 166 | return MS_SUCCESS; |
184 | 169 | } |
185 | | |
186 | | /* msCloseErrorFile() |
187 | | ** |
188 | | ** Close current output file (if one is open) and reset related members |
189 | | */ |
190 | 169 | void msCloseErrorFile() { |
191 | 169 | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
192 | | |
193 | 169 | if (debuginfo && debuginfo->debug_mode != MS_DEBUGMODE_OFF) { |
194 | 165 | if (debuginfo->fp && debuginfo->debug_mode == MS_DEBUGMODE_FILE) |
195 | 94 | fclose(debuginfo->fp); |
196 | | |
197 | 165 | if (debuginfo->fp && (debuginfo->debug_mode == MS_DEBUGMODE_STDERR || |
198 | 165 | debuginfo->debug_mode == MS_DEBUGMODE_STDOUT)) |
199 | 71 | fflush(debuginfo->fp); /* just flush stderr or stdout */ |
200 | | |
201 | 165 | debuginfo->fp = NULL; |
202 | | |
203 | 165 | msFree(debuginfo->errorfile); |
204 | 165 | debuginfo->errorfile = NULL; |
205 | | |
206 | 165 | debuginfo->debug_mode = MS_DEBUGMODE_OFF; |
207 | 165 | } |
208 | 169 | } |
209 | | |
210 | | /* msGetErrorFile() |
211 | | ** |
212 | | ** Returns name of current error file |
213 | | ** |
214 | | ** Returns NULL if not set. |
215 | | */ |
216 | 0 | const char *msGetErrorFile() { |
217 | 0 | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
218 | |
|
219 | 0 | if (debuginfo) |
220 | 0 | return debuginfo->errorfile; |
221 | | |
222 | 0 | return NULL; |
223 | 0 | } |
224 | | |
225 | | /* Set/Get the current global debug level value, used as default value for |
226 | | ** new map and layer objects and to control msDebug() calls outside of |
227 | | ** the context of mapObj or layerObj. |
228 | | ** |
229 | | */ |
230 | 0 | void msSetGlobalDebugLevel(int level) { |
231 | 0 | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
232 | |
|
233 | 0 | if (debuginfo) |
234 | 0 | debuginfo->global_debug_level = (debugLevel)level; |
235 | 0 | } |
236 | | |
237 | 64.8k | debugLevel msGetGlobalDebugLevel() { |
238 | 64.8k | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
239 | | |
240 | 64.8k | if (debuginfo) |
241 | 64.8k | return debuginfo->global_debug_level; |
242 | | |
243 | 0 | return MS_DEBUGLEVEL_ERRORSONLY; |
244 | 64.8k | } |
245 | | |
246 | | /* msDebugInitFromEnv() |
247 | | ** |
248 | | ** Init debug state from MS_ERRORFILE and MS_DEBUGLEVEL env vars if set |
249 | | ** |
250 | | ** Returns MS_SUCCESS/MS_FAILURE |
251 | | */ |
252 | 0 | int msDebugInitFromEnv() { |
253 | 0 | const char *val; |
254 | |
|
255 | 0 | if ((val = CPLGetConfigOption("MS_ERRORFILE", NULL)) != NULL) { |
256 | 0 | if (msSetErrorFile(val, NULL) != MS_SUCCESS) |
257 | 0 | return MS_FAILURE; |
258 | 0 | } |
259 | | |
260 | 0 | if ((val = CPLGetConfigOption("MS_DEBUGLEVEL", NULL)) != NULL) |
261 | 0 | msSetGlobalDebugLevel(atoi(val)); |
262 | |
|
263 | 0 | return MS_SUCCESS; |
264 | 0 | } |
265 | | |
266 | | /* msDebugCleanup() |
267 | | ** |
268 | | ** Called by msCleanup to remove info related to this thread. |
269 | | */ |
270 | 0 | void msDebugCleanup() { |
271 | | /* make sure file is closed */ |
272 | 0 | msCloseErrorFile(); |
273 | |
|
274 | | #ifdef USE_THREAD |
275 | | { |
276 | | void *thread_id = msGetThreadId(); |
277 | | debugInfoObj *link; |
278 | | |
279 | | msAcquireLock(TLOCK_DEBUGOBJ); |
280 | | |
281 | | /* find link for this thread */ |
282 | | |
283 | | for (link = debuginfo_list; |
284 | | link != NULL && link->thread_id != thread_id && link->next != NULL && |
285 | | link->next->thread_id != thread_id; |
286 | | link = link->next) { |
287 | | } |
288 | | |
289 | | if (link->thread_id == thread_id) { |
290 | | /* presumably link is at head of list. */ |
291 | | if (debuginfo_list == link) |
292 | | debuginfo_list = link->next; |
293 | | |
294 | | free(link); |
295 | | } else if (link->next != NULL && link->next->thread_id == thread_id) { |
296 | | debugInfoObj *next_link = link->next; |
297 | | link->next = link->next->next; |
298 | | free(next_link); |
299 | | } |
300 | | msReleaseLock(TLOCK_DEBUGOBJ); |
301 | | } |
302 | | #endif |
303 | 0 | } |
304 | | |
305 | | /* msDebug() |
306 | | ** |
307 | | ** Outputs/logs messages to the MS_ERRORFILE if one is set |
308 | | ** (see msSetErrorFile()) |
309 | | ** |
310 | | */ |
311 | 19.2k | void msDebug(const char *pszFormat, ...) { |
312 | 19.2k | va_list args; |
313 | 19.2k | debugInfoObj *debuginfo = msGetDebugInfoObj(); |
314 | 19.2k | char szMessage[MESSAGELENGTH]; |
315 | | |
316 | 19.2k | if (debuginfo == NULL || debuginfo->debug_mode == MS_DEBUGMODE_OFF) |
317 | 4.97k | return; /* Don't waste time here! */ |
318 | | |
319 | 14.2k | va_start(args, pszFormat); |
320 | 14.2k | vsnprintf(szMessage, MESSAGELENGTH, pszFormat, args); |
321 | 14.2k | va_end(args); |
322 | 14.2k | szMessage[MESSAGELENGTH - 1] = '\0'; |
323 | | |
324 | 14.2k | msRedactCredentials(szMessage); |
325 | | |
326 | 14.2k | if (debuginfo->fp) { |
327 | | /* Writing to a stdio file handle */ |
328 | | |
329 | | #if defined(USE_FASTCGI) |
330 | | /* It seems the FastCGI stuff inserts a timestamp anyways, so */ |
331 | | /* we might as well skip this one if writing to stderr w/ FastCGI. */ |
332 | | if (debuginfo->debug_mode != MS_DEBUGMODE_STDERR) |
333 | | #endif |
334 | 14.2k | { |
335 | 14.2k | struct mstimeval tv; |
336 | 14.2k | time_t t; |
337 | 14.2k | msGettimeofday(&tv, NULL); |
338 | 14.2k | t = tv.tv_sec; |
339 | 14.2k | msIO_fprintf(debuginfo->fp, "[%s].%ld ", msStringChop(ctime(&t)), |
340 | 14.2k | (long)tv.tv_usec); |
341 | 14.2k | } |
342 | | |
343 | 14.2k | msIO_fprintf(debuginfo->fp, "%s", szMessage); |
344 | 14.2k | } |
345 | | #ifdef _WIN32 |
346 | | else if (debuginfo->debug_mode == MS_DEBUGMODE_WINDOWSDEBUG) { |
347 | | /* Writing to Windows Debug Console */ |
348 | | OutputDebugStringA(szMessage); |
349 | | } |
350 | | #endif |
351 | 14.2k | } |
352 | | |
353 | | /* msDebug2() |
354 | | ** |
355 | | ** Variadic function with no operation |
356 | | ** |
357 | | */ |
358 | 0 | void msDebug2(int level, ...) { (void)level; } |