1e1ddddf9STrond Norbye/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2e1ddddf9STrond Norbye/**
3e1ddddf9STrond Norbye * @todo "chain" the loggers - I should use the next logger instead of stderr
4e1ddddf9STrond Norbye * @todo don't format into a temporary buffer, but directly into the
5e1ddddf9STrond Norbye *       destination buffer
6e1ddddf9STrond Norbye */
7e1ddddf9STrond Norbye#include "config.h"
8e1ddddf9STrond Norbye#include <stdarg.h>
9e1ddddf9STrond Norbye#include <stdio.h>
10e1ddddf9STrond Norbye#include <errno.h>
11e1ddddf9STrond Norbye#include <string.h>
12f603fdb6STrond Norbye#include <strings.h>
13e1ddddf9STrond Norbye#include <stdlib.h>
149353b24dSTrond Norbye#include <time.h>
159353b24dSTrond Norbye
169353b24dSTrond Norbye#ifdef WIN32
179353b24dSTrond Norbye#include <io.h>
189353b24dSTrond Norbye#define F_OK 00
199353b24dSTrond Norbye#endif
20f603fdb6STrond Norbye
21f603fdb6STrond Norbye#include <platform/platform.h>
22e1ddddf9STrond Norbye
237f73dd94STrond Norbye#ifdef WIN32_H
247f73dd94STrond Norbye#undef close
257f73dd94STrond Norbye#endif
267f73dd94STrond Norbye
27e1ddddf9STrond Norbye#include <memcached/extension.h>
28e1ddddf9STrond Norbye#include <memcached/engine.h>
29e1ddddf9STrond Norbye
30f603fdb6STrond Norbye#include "extensions/protocol_extension.h"
31e1ddddf9STrond Norbye
32e1ddddf9STrond Norbye/* Pointer to the server API */
33e1ddddf9STrond Norbyestatic SERVER_HANDLE_V1 *sapi;
34e1ddddf9STrond Norbye
35e1ddddf9STrond Norbye/* The current log level set by the user. We should ignore all log requests
36e1ddddf9STrond Norbye * with a finer log level than this. We've registered a listener to update
37e1ddddf9STrond Norbye * the log level when the user change it
38e1ddddf9STrond Norbye */
39e1ddddf9STrond Norbyestatic EXTENSION_LOG_LEVEL current_log_level = EXTENSION_LOG_WARNING;
40e1ddddf9STrond Norbye
41e1ddddf9STrond Norbye/* All messages above the current level shall be sent to stderr immediately */
42e1ddddf9STrond Norbyestatic EXTENSION_LOG_LEVEL output_level = EXTENSION_LOG_WARNING;
43e1ddddf9STrond Norbye
44e1ddddf9STrond Norbye/* To avoid the logfile to grow forever, we'll start logging to another
45e1ddddf9STrond Norbye * file when we've added a certain amount of data to the logfile. You may
46e1ddddf9STrond Norbye * tune this size by using the "cyclesize" configuration parameter. Use 100MB
47e1ddddf9STrond Norbye * as the default (makes it a reasonable size to work with in your favorite
48e1ddddf9STrond Norbye * editor ;-))
49e1ddddf9STrond Norbye */
50e1ddddf9STrond Norbyestatic size_t cyclesz = 100 * 1024 * 1024;
51e1ddddf9STrond Norbye
52e1ddddf9STrond Norbye/*
53e1ddddf9STrond Norbye * We're using two buffers for logging. We'll be inserting data into one,
54e1ddddf9STrond Norbye * while we're working on writing the other one to disk. Given that the disk
55e1ddddf9STrond Norbye * is way slower than our CPU, we might end up in a situation that we'll be
56e1ddddf9STrond Norbye * blocking the frontend threads if you're logging too much.
57e1ddddf9STrond Norbye */
58f54090cbSTrond Norbyestatic struct logbuffer {
59e1ddddf9STrond Norbye    /* Pointer to beginning of the datasegment of this buffer */
60e1ddddf9STrond Norbye    char *data;
61e1ddddf9STrond Norbye    /* The current offset of the buffer */
62e1ddddf9STrond Norbye    size_t offset;
63e1ddddf9STrond Norbye} buffers[2];
64e1ddddf9STrond Norbye
65e1ddddf9STrond Norbye/* The index in the buffers where we're currently inserting more data */
66e1ddddf9STrond Norbyestatic int currbuffer;
67e1ddddf9STrond Norbye
689cfaa08bSTrond Norbye/* If we should try to pretty-print the severity or not */
699cfaa08bSTrond Norbyestatic bool prettyprint = false;
709cfaa08bSTrond Norbye
71b2c13f5aSTrond Norbye/* Are we running in a unit test (don't print warnings to stderr) */
72b2c13f5aSTrond Norbyestatic bool unit_test = false;
73b2c13f5aSTrond Norbye
74e1ddddf9STrond Norbye/* The size of the buffers (this may be tuned by the buffersize configuration
75e1ddddf9STrond Norbye * parameter */
76e1ddddf9STrond Norbyestatic size_t buffersz = 2048 * 1024;
77e1ddddf9STrond Norbye
78185e37e1STrond Norbye/* The sleeptime between each forced flush of the buffer */
79185e37e1STrond Norbyestatic size_t sleeptime = 60;
80185e37e1STrond Norbye
81e1ddddf9STrond Norbye/* To avoid race condition we're protecting our shared resources with a
82e1ddddf9STrond Norbye * single mutex. */
83f603fdb6STrond Norbyestatic cb_mutex_t mutex;
84e1ddddf9STrond Norbye
85e1ddddf9STrond Norbye/* The thread performing the disk IO will be waiting for the input buffers
86e1ddddf9STrond Norbye * to be filled by sleeping on the following condition variable. The
87e1ddddf9STrond Norbye * frontend threads will notify the condition variable when the buffer is
88e1ddddf9STrond Norbye * > 75% full
89e1ddddf9STrond Norbye */
90f603fdb6STrond Norbyestatic cb_cond_t cond;
91e1ddddf9STrond Norbye
92e1ddddf9STrond Norbye/* In the "worst case scenarios" we're logging so much that the disk thread
93e1ddddf9STrond Norbye * can't keep up with the the frontend threads. In these rare situations
94e1ddddf9STrond Norbye * the frontend threads will block and wait for the flusher to free up log
95e1ddddf9STrond Norbye * space
96e1ddddf9STrond Norbye */
97f603fdb6STrond Norbyestatic cb_cond_t space_cond;
98e1ddddf9STrond Norbye
99b07bbfe5STrond Norbyetypedef void * HANDLE;
100b07bbfe5STrond Norbye
101b07bbfe5STrond Norbyestatic HANDLE stdio_open(const char *path, const char *mode) {
102b07bbfe5STrond Norbye    HANDLE ret = fopen(path, mode);
103b07bbfe5STrond Norbye    if (ret) {
104b07bbfe5STrond Norbye        setbuf(ret, NULL);
105b07bbfe5STrond Norbye    }
106b07bbfe5STrond Norbye    return ret;
107b07bbfe5STrond Norbye}
108b07bbfe5STrond Norbye
109b07bbfe5STrond Norbyestatic void stdio_close(HANDLE handle) {
110b07bbfe5STrond Norbye    (void)fclose(handle);
111b07bbfe5STrond Norbye}
112b07bbfe5STrond Norbye
1136ae7c28bSTrond Norbyestatic void stdio_flush(HANDLE handle) {
114b07bbfe5STrond Norbye    fflush(handle);
115b07bbfe5STrond Norbye}
116b07bbfe5STrond Norbye
117b07bbfe5STrond Norbyestatic ssize_t stdio_write(HANDLE handle, const void *ptr, size_t nbytes) {
118b07bbfe5STrond Norbye    return (ssize_t)fwrite(ptr, 1, nbytes, handle);
119b07bbfe5STrond Norbye}
120b07bbfe5STrond Norbye
121b07bbfe5STrond Norbyestruct io_ops {
122b07bbfe5STrond Norbye    HANDLE (*open)(const char *path, const char *mode);
123b07bbfe5STrond Norbye    void (*close)(HANDLE handle);
1246ae7c28bSTrond Norbye    void (*flush)(HANDLE handle);
125b07bbfe5STrond Norbye    ssize_t (*write)(HANDLE handle, const void *ptr, size_t nbytes);
1266ae7c28bSTrond Norbye} iops;
127f603fdb6STrond Norbye
128b07bbfe5STrond Norbyestatic const char *extension = "txt";
129b07bbfe5STrond Norbye
130e1ddddf9STrond Norbyestatic void add_log_entry(const char *msg, size_t size)
131e1ddddf9STrond Norbye{
132f603fdb6STrond Norbye    cb_mutex_enter(&mutex);
133e1ddddf9STrond Norbye    /* wait until there is room in the current buffer */
134e1ddddf9STrond Norbye    while ((buffers[currbuffer].offset + size) >= buffersz) {
135b2c13f5aSTrond Norbye        if (!unit_test) {
136b2c13f5aSTrond Norbye            fprintf(stderr, "WARNING: waiting for log space to be available\n");
137b2c13f5aSTrond Norbye        }
138f603fdb6STrond Norbye        cb_cond_wait(&space_cond, &mutex);
139e1ddddf9STrond Norbye    }
140e1ddddf9STrond Norbye
141e1ddddf9STrond Norbye    /* We could have performed the memcpy outside the locked region,
142e1ddddf9STrond Norbye     * but then we would need to handle the situation where we're
143e1ddddf9STrond Norbye     * flipping the ownership of the buffer (otherwise we could be
144e1ddddf9STrond Norbye     * writing rubbish to the file) */
145e1ddddf9STrond Norbye    memcpy(buffers[currbuffer].data + buffers[currbuffer].offset,
146e1ddddf9STrond Norbye           msg, size);
147e1ddddf9STrond Norbye    buffers[currbuffer].offset += size;
148e1ddddf9STrond Norbye    if (buffers[currbuffer].offset > (buffersz * 0.75)) {
149e1ddddf9STrond Norbye        /* we're getting full.. time get the logger to start doing stuff! */
150f603fdb6STrond Norbye        cb_cond_signal(&cond);
151e1ddddf9STrond Norbye    }
152f603fdb6STrond Norbye    cb_mutex_exit(&mutex);
153e1ddddf9STrond Norbye}
154e1ddddf9STrond Norbye
1559cfaa08bSTrond Norbyestatic const char *severity2string(EXTENSION_LOG_LEVEL sev) {
1569cfaa08bSTrond Norbye    switch (sev) {
1579cfaa08bSTrond Norbye    case EXTENSION_LOG_WARNING:
1589cfaa08bSTrond Norbye        return "WARNING";
1599cfaa08bSTrond Norbye    case EXTENSION_LOG_INFO:
160b2c13f5aSTrond Norbye        return "INFO";
1619cfaa08bSTrond Norbye    case EXTENSION_LOG_DEBUG:
162b2c13f5aSTrond Norbye        return "DEBUG";
1639cfaa08bSTrond Norbye    case EXTENSION_LOG_DETAIL:
164b2c13f5aSTrond Norbye        return "DETAIL";
1659cfaa08bSTrond Norbye    default:
166b2c13f5aSTrond Norbye        return "????";
1679cfaa08bSTrond Norbye    }
1689cfaa08bSTrond Norbye}
1699cfaa08bSTrond Norbye
170e1ddddf9STrond Norbyestatic void logger_log(EXTENSION_LOG_LEVEL severity,
171e1ddddf9STrond Norbye                       const void* client_cookie,
172e1ddddf9STrond Norbye                       const char *fmt, ...)
173e1ddddf9STrond Norbye{
174e1ddddf9STrond Norbye    (void)client_cookie;
175e1ddddf9STrond Norbye    if (severity >= current_log_level || severity >= output_level) {
176e1ddddf9STrond Norbye        /* @fixme: We shouldn't have to go through this temporary
177e1ddddf9STrond Norbye         *         buffer, but rather insert the data directly into
178e1ddddf9STrond Norbye         *         the destination buffer
179e1ddddf9STrond Norbye         */
180e1ddddf9STrond Norbye        char buffer[2048];
181e1ddddf9STrond Norbye        size_t avail = sizeof(buffer) - 1;
1829cfaa08bSTrond Norbye        int prefixlen = 0;
183f603fdb6STrond Norbye        va_list ap;
1849353b24dSTrond Norbye        size_t len;
1859cfaa08bSTrond Norbye        struct timeval now;
186f603fdb6STrond Norbye
187e73832dcSjim        if (cb_get_timeofday(&now) == 0) {
1889cfaa08bSTrond Norbye            struct tm tval;
1899cfaa08bSTrond Norbye            time_t nsec = (time_t)now.tv_sec;
190fe902774STrond Norbye            char str[40];
1919353b24dSTrond Norbye            int error;
1929353b24dSTrond Norbye
1939353b24dSTrond Norbye#ifdef WIN32
1949353b24dSTrond Norbye            localtime_s(&tval, &nsec);
1959353b24dSTrond Norbye            error = (asctime_s(str, sizeof(str), &tval) != 0);
1969353b24dSTrond Norbye#else
197f603fdb6STrond Norbye            localtime_r(&nsec, &tval);
1989353b24dSTrond Norbye            error = (asctime_r(&tval, str) == NULL);
1999353b24dSTrond Norbye#endif
2009353b24dSTrond Norbye
2019353b24dSTrond Norbye            if (error) {
2029cfaa08bSTrond Norbye                prefixlen = snprintf(buffer, avail, "%u.%06u",
2039cfaa08bSTrond Norbye                                     (unsigned int)now.tv_sec,
2049cfaa08bSTrond Norbye                                     (unsigned int)now.tv_usec);
2059cfaa08bSTrond Norbye            } else {
206524945feSTrond Norbye                const char *tz;
207524945feSTrond Norbye#ifdef HAVE_TM_ZONE
208524945feSTrond Norbye                tz = tval.tm_zone;
209524945feSTrond Norbye#else
210524945feSTrond Norbye                tz = tzname[tval.tm_isdst ? 1 : 0];
211524945feSTrond Norbye#endif
2129cfaa08bSTrond Norbye                /* trim off ' YYYY\n' */
2139cfaa08bSTrond Norbye                str[strlen(str) - 6] = '\0';
214fe902774STrond Norbye                prefixlen = snprintf(buffer, avail, "%s.%06u %s",
215fe902774STrond Norbye                                     str, (unsigned int)now.tv_usec,
216524945feSTrond Norbye                                     tz);
2179cfaa08bSTrond Norbye            }
2189cfaa08bSTrond Norbye        } else {
2199cfaa08bSTrond Norbye            fprintf(stderr, "gettimeofday failed: %s\n", strerror(errno));
2209cfaa08bSTrond Norbye            return;
2219cfaa08bSTrond Norbye        }
2229cfaa08bSTrond Norbye
2239cfaa08bSTrond Norbye        if (prettyprint) {
2249cfaa08bSTrond Norbye            prefixlen += snprintf(buffer+prefixlen, avail-prefixlen,
2259cfaa08bSTrond Norbye                                  " %s: ", severity2string(severity));
2269cfaa08bSTrond Norbye        } else {
2279cfaa08bSTrond Norbye            prefixlen += snprintf(buffer+prefixlen, avail-prefixlen,
2289cfaa08bSTrond Norbye                                  " %u: ", (unsigned int)severity);
2299cfaa08bSTrond Norbye        }
230e1ddddf9STrond Norbye
231e1ddddf9STrond Norbye        avail -= prefixlen;
232e1ddddf9STrond Norbye        va_start(ap, fmt);
233f603fdb6STrond Norbye        len = vsnprintf(buffer + prefixlen, avail, fmt, ap);
234e1ddddf9STrond Norbye        va_end(ap);
235e1ddddf9STrond Norbye
236e1ddddf9STrond Norbye        if (len < avail) {
237e1ddddf9STrond Norbye            len += prefixlen;
238e1ddddf9STrond Norbye            if (buffer[len - 1] != '\n') {
239e1ddddf9STrond Norbye                buffer[len++] = '\n';
240e1ddddf9STrond Norbye                buffer[len] ='\0';
241e1ddddf9STrond Norbye            }
242e1ddddf9STrond Norbye
243e1ddddf9STrond Norbye            if (severity >= output_level) {
244e1ddddf9STrond Norbye                fputs(buffer, stderr);
245e1ddddf9STrond Norbye                fflush(stderr);
246e1ddddf9STrond Norbye            }
247e1ddddf9STrond Norbye
248e1ddddf9STrond Norbye            if (severity >= current_log_level) {
249e1ddddf9STrond Norbye                add_log_entry(buffer, len);
250e1ddddf9STrond Norbye            }
251e1ddddf9STrond Norbye        } else {
252e1ddddf9STrond Norbye            fprintf(stderr, "Log message dropped... too big\n");
253e1ddddf9STrond Norbye        }
254e1ddddf9STrond Norbye    }
255e1ddddf9STrond Norbye}
256e1ddddf9STrond Norbye
257b07bbfe5STrond Norbyestatic HANDLE open_logfile(const char *fnm) {
258e1ddddf9STrond Norbye    static unsigned int next_id = 0;
259e1ddddf9STrond Norbye    char fname[1024];
260f603fdb6STrond Norbye    HANDLE ret;
261e1ddddf9STrond Norbye    do {
262e1ddddf9STrond Norbye        sprintf(fname, "%s.%d.%s", fnm, next_id++, extension);
263e1ddddf9STrond Norbye    } while (access(fname, F_OK) == 0);
264f603fdb6STrond Norbye    ret = iops.open(fname, "wb");
265b07bbfe5STrond Norbye    if (!ret) {
266e1ddddf9STrond Norbye        fprintf(stderr, "Failed to open memcached log file\n");
267e1ddddf9STrond Norbye    }
268e1ddddf9STrond Norbye    return ret;
269e1ddddf9STrond Norbye}
270e1ddddf9STrond Norbye
271b07bbfe5STrond Norbyestatic void close_logfile(HANDLE fp) {
272e1ddddf9STrond Norbye    if (fp) {
273b07bbfe5STrond Norbye        iops.close(fp);
274e1ddddf9STrond Norbye    }
275e1ddddf9STrond Norbye}
276e1ddddf9STrond Norbye
277b07bbfe5STrond Norbyestatic HANDLE reopen_logfile(HANDLE old, const char *fnm) {
278e1ddddf9STrond Norbye    close_logfile(old);
279e1ddddf9STrond Norbye    return open_logfile(fnm);
280e1ddddf9STrond Norbye}
281e1ddddf9STrond Norbye
282b07bbfe5STrond Norbyestatic size_t flush_pending_io(HANDLE file, struct logbuffer *lb) {
283e1ddddf9STrond Norbye    size_t ret = 0;
284e1ddddf9STrond Norbye    if (lb->offset > 0) {
285e1ddddf9STrond Norbye        char *ptr = lb->data;
286e1ddddf9STrond Norbye        size_t towrite = ret = lb->offset;
287e1ddddf9STrond Norbye
288e1ddddf9STrond Norbye        while (towrite > 0) {
289b07bbfe5STrond Norbye            int nw = iops.write(file, ptr, towrite);
290e1ddddf9STrond Norbye            if (nw > 0) {
291e1ddddf9STrond Norbye                ptr += nw;
292e1ddddf9STrond Norbye                towrite -= nw;
293e1ddddf9STrond Norbye            }
294e1ddddf9STrond Norbye        }
295e1ddddf9STrond Norbye        lb->offset = 0;
2966ae7c28bSTrond Norbye        iops.flush(file);
297e1ddddf9STrond Norbye    }
298e1ddddf9STrond Norbye
299e1ddddf9STrond Norbye    return ret;
300e1ddddf9STrond Norbye}
301e1ddddf9STrond Norbye
302e1ddddf9STrond Norbyestatic volatile int run = 1;
303f603fdb6STrond Norbyestatic cb_thread_t tid;
304e1ddddf9STrond Norbye
305f603fdb6STrond Norbyestatic void logger_thead_main(void* arg)
306e1ddddf9STrond Norbye{
307e1ddddf9STrond Norbye    size_t currsize = 0;
308b07bbfe5STrond Norbye    HANDLE fp = open_logfile(arg);
310e73832dcSjim    struct timeval tp;
311e73832dcSjim    cb_get_timeofday(&tp);
312e73832dcSjim    time_t next = (time_t)tp.tv_sec;
313e1ddddf9STrond Norbye
314f603fdb6STrond Norbye    cb_mutex_enter(&mutex);
315e1ddddf9STrond Norbye    while (run) {
316e73832dcSjim        cb_get_timeofday(&tp);
317185e37e1STrond Norbye
3189353b24dSTrond Norbye        while ((time_t)tp.tv_sec >= next  ||
319185e37e1STrond Norbye               buffers[currbuffer].offset > (buffersz * 0.75)) {
320e1ddddf9STrond Norbye            int this  = currbuffer;
3219353b24dSTrond Norbye            next = (time_t)tp.tv_sec + 1;
322e1ddddf9STrond Norbye            currbuffer = (currbuffer == 0) ? 1 : 0;
323e1ddddf9STrond Norbye            /* Let people who is blocked for space continue */
324f603fdb6STrond Norbye            cb_cond_broadcast(&space_cond);
325e1ddddf9STrond Norbye
326e1ddddf9STrond Norbye            /* Perform file IO without the lock */
327f603fdb6STrond Norbye            cb_mutex_exit(&mutex);
328e1ddddf9STrond Norbye
329e1ddddf9STrond Norbye            currsize += flush_pending_io(fp, buffers + this);
330e1ddddf9STrond Norbye            if (currsize > cyclesz) {
331e1ddddf9STrond Norbye                fp = reopen_logfile(fp, arg);
332e1ddddf9STrond Norbye                currsize = 0;
333e1ddddf9STrond Norbye            }
334f603fdb6STrond Norbye            cb_mutex_enter(&mutex);
335e1ddddf9STrond Norbye        }
336e1ddddf9STrond Norbye
337e73832dcSjim        cb_get_timeofday(&tp);
3389353b24dSTrond Norbye        next = (time_t)tp.tv_sec + (time_t)sleeptime;
339b2c13f5aSTrond Norbye        if (unit_test) {
340b2c13f5aSTrond Norbye            cb_cond_timedwait(&cond, &mutex, 100);
341b2c13f5aSTrond Norbye        } else {
342b2c13f5aSTrond Norbye            cb_cond_timedwait(&cond, &mutex, 1000 * sleeptime);
343b2c13f5aSTrond Norbye        }
344e1ddddf9STrond Norbye    }
345e1ddddf9STrond Norbye
346e1ddddf9STrond Norbye    if (fp) {
347e1ddddf9STrond Norbye        while (buffers[currbuffer].offset) {
348e1ddddf9STrond Norbye            int this  = currbuffer;
349e1ddddf9STrond Norbye            currbuffer = (currbuffer == 0) ? 1 : 0;
350e1ddddf9STrond Norbye            flush_pending_io(fp, buffers + this);
351e1ddddf9STrond Norbye        }
352e1ddddf9STrond Norbye        close_logfile(fp);
353e1ddddf9STrond Norbye    }
354e1ddddf9STrond Norbye
355f603fdb6STrond Norbye    cb_mutex_exit(&mutex);
356e1ddddf9STrond Norbye    free(arg);
357e1ddddf9STrond Norbye    free(buffers[0].data);
358e1ddddf9STrond Norbye    free(buffers[1].data);
359e1ddddf9STrond Norbye}
360e1ddddf9STrond Norbye
361e1ddddf9STrond Norbyestatic void exit_handler(void) {
362fd7677dfSTrond Norbye    /* Unfortunately it looks like the C runtime from MSVC "kills" the
363fd7677dfSTrond Norbye     * threads before the "atexit" handler is run, causing the program
364fd7677dfSTrond Norbye     * to halt in one of these steps depending on the state of the
365fd7677dfSTrond Norbye     * variables. Just disable the code for now.
366fd7677dfSTrond Norbye     */
367fd7677dfSTrond Norbye#ifndef WIN32
368f603fdb6STrond Norbye    cb_mutex_enter(&mutex);
369e1ddddf9STrond Norbye    run = 0;
370f603fdb6STrond Norbye    cb_cond_signal(&cond);
371f603fdb6STrond Norbye    cb_mutex_exit(&mutex);
372e1ddddf9STrond Norbye
373f603fdb6STrond Norbye    cb_join_thread(tid);
374fd7677dfSTrond Norbye#endif
375e1ddddf9STrond Norbye}
376e1ddddf9STrond Norbye
377e1ddddf9STrond Norbyestatic const char *get_name(void) {
3786ae7c28bSTrond Norbye    return "file logger";
379e1ddddf9STrond Norbye}
380e1ddddf9STrond Norbye
381f603fdb6STrond Norbyestatic EXTENSION_LOGGER_DESCRIPTOR descriptor;
382e1ddddf9STrond Norbye
383e1ddddf9STrond Norbyestatic void on_log_level(const void *cookie, ENGINE_EVENT_TYPE type,
384e1ddddf9STrond Norbye                         const void *event_data, const void *cb_data) {
385e1ddddf9STrond Norbye    if (sapi != NULL) {
386e1ddddf9STrond Norbye        current_log_level = sapi->log->get_level();
387e1ddddf9STrond Norbye    }
388e1ddddf9STrond Norbye}
389e1ddddf9STrond Norbye
390b2c13f5aSTrond Norbyestatic void logger_shutdown(void)  {
391b2c13f5aSTrond Norbye    int running;
392b2c13f5aSTrond Norbye    cb_mutex_enter(&mutex);
393b2c13f5aSTrond Norbye    running = run;
394b2c13f5aSTrond Norbye    run = 0;
395b2c13f5aSTrond Norbye    cb_cond_signal(&cond);
396b2c13f5aSTrond Norbye    cb_mutex_exit(&mutex);
397b2c13f5aSTrond Norbye
398b2c13f5aSTrond Norbye    if (running) {
399b2c13f5aSTrond Norbye        cb_join_thread(tid);
400b2c13f5aSTrond Norbye    }
401b2c13f5aSTrond Norbye}
402b2c13f5aSTrond Norbye
403e1ddddf9STrond NorbyeMEMCACHED_PUBLIC_API
404e1ddddf9STrond NorbyeEXTENSION_ERROR_CODE memcached_extensions_initialize(const char *config,
405c5a4e2ebSTrond Norbye                                                     GET_SERVER_API get_server_api)
406c5a4e2ebSTrond Norbye{
407f603fdb6STrond Norbye    char *fname = NULL;
408f603fdb6STrond Norbye
409f603fdb6STrond Norbye    cb_mutex_initialize(&mutex);
410f603fdb6STrond Norbye    cb_cond_initialize(&cond);
411f603fdb6STrond Norbye    cb_cond_initialize(&space_cond);
412f603fdb6STrond Norbye
413f603fdb6STrond Norbye    iops.open = stdio_open;
414f603fdb6STrond Norbye    iops.close = stdio_close;
415f603fdb6STrond Norbye    iops.flush = stdio_flush;
416f603fdb6STrond Norbye    iops.write = stdio_write;
417f603fdb6STrond Norbye    descriptor.get_name = get_name;
418f603fdb6STrond Norbye    descriptor.log = logger_log;
419b2c13f5aSTrond Norbye    descriptor.shutdown = logger_shutdown;
420f603fdb6STrond Norbye
421c5a4e2ebSTrond Norbye#ifdef HAVE_TM_ZONE
422c5a4e2ebSTrond Norbye    tzset();
423c5a4e2ebSTrond Norbye#endif
424c5a4e2ebSTrond Norbye
425e1ddddf9STrond Norbye    sapi = get_server_api();
426e1ddddf9STrond Norbye    if (sapi == NULL) {
427e1ddddf9STrond Norbye        return EXTENSION_FATAL;
428e1ddddf9STrond Norbye    }
429e1ddddf9STrond Norbye
430e1ddddf9STrond Norbye    if (config != NULL) {
431e1ddddf9STrond Norbye        char *loglevel = NULL;
4326ae7c28bSTrond Norbye        struct config_item items[8];
433f603fdb6STrond Norbye        int ii = 0;
434f603fdb6STrond Norbye        memset(&items, 0, sizeof(items));
435f603fdb6STrond Norbye
436f603fdb6STrond Norbye        items[ii].key = "filename";
437f603fdb6STrond Norbye        items[ii].datatype = DT_STRING;
438f603fdb6STrond Norbye        items[ii].value.dt_string = &fname;
439f603fdb6STrond Norbye        ++ii;
440f603fdb6STrond Norbye
441f603fdb6STrond Norbye        items[ii].key = "buffersize";
442f603fdb6STrond Norbye        items[ii].datatype = DT_SIZE;
443f603fdb6STrond Norbye        items[ii].value.dt_size = &buffersz;
444f603fdb6STrond Norbye        ++ii;
445f603fdb6STrond Norbye
446f603fdb6STrond Norbye        items[ii].key = "cyclesize";
447f603fdb6STrond Norbye        items[ii].datatype = DT_SIZE;
448f603fdb6STrond Norbye        items[ii].value.dt_size = &cyclesz;
449f603fdb6STrond Norbye        ++ii;
450f603fdb6STrond Norbye
451f603fdb6STrond Norbye        items[ii].key = "loglevel";
452f603fdb6STrond Norbye        items[ii].datatype = DT_STRING;
453f603fdb6STrond Norbye        items[ii].value.dt_string = &loglevel;
454f603fdb6STrond Norbye        ++ii;
455f603fdb6STrond Norbye
456f603fdb6STrond Norbye        items[ii].key = "prettyprint";
457f603fdb6STrond Norbye        items[ii].datatype = DT_BOOL;
458f603fdb6STrond Norbye        items[ii].value.dt_bool = &prettyprint;
459f603fdb6STrond Norbye        ++ii;
460f603fdb6STrond Norbye
461f603fdb6STrond Norbye        items[ii].key = "sleeptime";
462f603fdb6STrond Norbye        items[ii].datatype = DT_SIZE;
463f603fdb6STrond Norbye        items[ii].value.dt_size = &sleeptime;
464f603fdb6STrond Norbye        ++ii;
465f603fdb6STrond Norbye
466b2c13f5aSTrond Norbye        items[ii].key = "unit_test";
467b2c13f5aSTrond Norbye        items[ii].datatype = DT_BOOL;
468b2c13f5aSTrond Norbye        items[ii].value.dt_bool = &unit_test;
469b2c13f5aSTrond Norbye        ++ii;
470b2c13f5aSTrond Norbye
471f603fdb6STrond Norbye        items[ii].key = NULL;
472f603fdb6STrond Norbye        ++ii;
4736ae7c28bSTrond Norbye        cb_assert(ii == 8);
474e1ddddf9STrond Norbye
475e1ddddf9STrond Norbye        if (sapi->core->parse_config(config, items, stderr) != ENGINE_SUCCESS) {
476e1ddddf9STrond Norbye            return EXTENSION_FATAL;
477e1ddddf9STrond Norbye        }
478e1ddddf9STrond Norbye
479e1ddddf9STrond Norbye        if (loglevel != NULL) {
480e1ddddf9STrond Norbye            if (strcasecmp("warning", loglevel) == 0) {
481e1ddddf9STrond Norbye