示例#1
0
class MemoryBuffer(BaseBuffer):

    buffer_chunk_limit = config_param("size", 32 * 1024**2)
    buffer_queue_limit = config_param("integer", 32)
    flush_interval = config_param('time', 5)

    def new_chunk(self, key, expire):
        return MemoryBufferChunk(key, expire)
示例#2
0
class StdoutOutput(Output):

    autoflush = config_param('bool', False)

    def emit(self, tag, es):
        for t, record in es:
            dt = datetime.fromtimestamp(t)
            print("%s %s: %s" % (dt, tag, json.dumps(record)))

        if self.autoflush:
            sys.stdout.flush()
示例#3
0
class ExecPipeOutput(BufferedOutput):

    command = config_param('string')

    def start(self):
        preexec = getattr(os, 'setsid', None)
        self._proc = subprocess.Popen(self.command,
                                      shell=True,
                                      stdin=subprocess.PIPE,
                                      preexec_fn=preexec)
        super(ExecPipeOutput, self).start()

    def shutdown(self):
        super(ExecPipeOutput, self).shutdown()
        self._proc.stdin.close()
        self._proc.wait()
        self._proc = None

    def write(self, chunk):
        self._proc.stdin.write(chunk.read())
示例#4
0
class ForwardInput(Input):

    port = config_param('integer', 9880)
    bind = config_param('string', '0.0.0.0')

    def start(self):
        log.info("start forward server on %s:%s", self.bind, self.port)
        self._server = StreamServer((self.bind, self.port), self.on_connect)
        self._server.start()
        self._hbserver = HeartbeatServer((self.bind, self.port))
        self._hbserver.start()

    def shutdown(self):
        self._hbserver.stop()
        self._server.stop()

    def on_message(self, msg):
        tag = msg[0]
        log.debug("on_message: recieved message %s", tag)
        entries = msg[1]
        ent_type = type(entries)

        if ent_type is bytes:
            Engine.emit_stream(tag, UnpackStream(entries))
        elif ent_type in (list, tuple):
            Engine.emit_stream(
                tag,
                [(e[0] or now(), e[1]) for e in entries],
            )
        else:
            Engine.emit(tag, msg[1] or now(), msg[2])

    def json_handler(self, data, sock):
        decode = json.JSONDecoder().raw_decode
        pos = 0
        while 1:
            try:
                obj, pos = decode(data, pos)
                self.on_message(obj)
            except (ValueError, StopIteration):
                data = data[pos:]
                next_data = sock.recv(128 * 1024)
                if not next_data:
                    break
                data += next_data
                pos = 0

    def mpack_handler(self, data, sock):
        unpacker = Unpacker()
        unpacker.feed(data)
        # default chunk size of memory buffer is 32MB
        RECV_SIZE = 32 * 1024 * 1024
        while 1:
            for msg in unpacker:
                self.on_message(msg)
            next_data = sock.recv(RECV_SIZE)
            if not next_data:
                break
            unpacker.feed(next_data)

    def on_connect(self, sock, addr):
        try:
            data = sock.recv(128 * 1024)
            if not data:
                return
            if data[0] in b'{[':
                self.json_handler(data, sock)
            else:
                self.mpack_handler(data, sock)
        finally:
            sock.close()
示例#5
0
class MultilogInput(Input):

    dir = tag = key = config_param('string')
    pos_file = config_param('string')

    def start(self):
        self._shutdown = False
        self._proc = gevent.spawn(self.run)

    def _open_log(self, inode=None, offset=None):
        pat = os.path.join(self.dir, '@*.s')
        current = os.path.join(self.dir, 'current')
        files = glob.glob(pat) + [current]
        files.sort()
        target = None
        if inode:
            for f in files:
                st = os.stat(f)
                if st.st_ino == inode:
                    target = f
                    break
        if not target:
            target = files[0]
            offset = 0

        fd = _open_nonblock(target)
        if offset:
            os.lseek(fd, offset, 0)
        return os.fstat(fd).st_ino, fd, offset

    def _open_next(self, fd):
        current = os.path.join(self.dir, 'current')
        current_fd = _open_nonblock(current)

        pat = os.path.join(self.dir, '@*.[su]')
        files = glob.glob(pat)
        files.sort()
        files.append(current)

        inode = os.fstat(fd).st_ino
        os.close(fd)

        for idx, f in enumerate(files):
            if os.stat(f).st_ino == inode:
                break
        else:
            log.warn("multilog: can't find before file. (inode=%d)", inode)
            return os.fstat(current_fd).st_ino, current_fd

        idx += 1
        if len(files) == idx:
            log.warn("multilog: rotate current => current?")
            return os.fstat(current_fd).st_ino, current_fd

        next_file = files[idx]
        if next_file == current:
            return os.fstat(current_fd).st_ino, current_fd

        os.close(current_fd)
        return os.stat(next_file).st_ino, _open_nonblock(next_file)

    def is_current(self, fd):
        current = os.path.join(self.dir, 'current')
        current_ino = os.stat(current).st_ino
        ino = os.fstat(fd).st_ino
        log.debug("current inode=%d, fd inode=%d", current_ino, ino)
        return ino == current_ino

    def readsome(self, fd):
        while 1:
            try:
                return os.read(fd, 1024**2)
            except OSError as e:
                if e.errno == errno.EAGAIN:
                    gevent.sleep(0.2)
                    continue
                raise

    def emit(self, line):
        tai, line = line.split(' ', 1)
        tai = int(tai[1:17], 16)
        if tai >= 2**62:
            tai -= 2**62
        else:
            tai = 0
        Engine.emit(self.tag, tai, {self.key: line})

    def run(self):
        db = gdbm.open(self.pos_file, 'cs')
        fd = None
        before = b''

        if 'pos' in db:
            pos = db['pos']
            inode, offset = map(int, pos.split(' '))
        else:
            inode = None
            offset = 0

        inode, fd, offset = self._open_log(inode, offset)
        read_pos = offset
        try:
            while not self._shutdown:
                buf = self.readsome(fd)

                if not buf:
                    if self.is_current(fd):
                        log.debug("waiting current. offset=%d", offset)
                        gevent.sleep(0.3)
                    else:
                        inode, fd = self._open_next(fd)
                        offset = 0
                    continue

                offset += len(buf)
                if '\n' not in buf:
                    before += buf
                    continue

                read_pos = offset
                lines = buf.splitlines(True)
                del buf
                lines[0] = before + lines[0]
                before = b''

                last = lines[-1]
                if not last.endswith('\n'):
                    read_pos -= len(last)
                    before = last
                    del lines[-1]

                for line in lines:
                    self.emit(line.rstrip())
                db['pos'] = "%d %d" % (inode, read_pos)
        finally:
            db['pos'] = '%d %d' % (inode, read_pos)
            db.close()
            if fd is not None:
                os.close(fd)

    def shutdown(self):
        self._shutdown = True
        self._proc.join()
示例#6
0
class TailInput(Input):

    path = tag = key = config_param('string')
    rotate_wait = config_param('time', default=5)

    def start(self):
        self._shutdown = False
        self._proc = gevent.spawn(self.run)

    @property
    def paths(self):
        return self._paths

    def configure(self, conf):
        super(TailInput, self).configure(conf)
        self._path = self.path.strip()

    def emit(self, line):
        Engine.emit(self.tag, int(time.time()), {self.key: line})

    def run(self):
        path = self._path
        fd = None
        before = b''
        try:
            while not self._shutdown:
                if fd is None:
                    try:
                        fd = os.open(path, os.O_NONBLOCK)
                        log.debug("Opened log file.")
                    except OSError:
                        log.debug("Couldn't open log file.")
                        gevent.sleep(self.rotate_wait)
                        continue

                buf = self.readsome(fd)

                if not buf:
                    if self.is_current(fd):
                        gevent.sleep(0.3)
                        continue
                    else:
                        gevent.sleep(self.rotate_wait)
                        buf = self.readsome(fd)
                        if not buf:
                            os.close(fd)
                            fd = None
                            log.debug("Closed log file.")
                            continue

                if b'\n' not in buf:
                    before += buf
                    continue

                buf = before + buf
                before = b''
                lines = buf.splitlines(True)

                if not lines[-1].endswith(b'\n'):
                    before = lines.pop()

                for line in lines:
                    self.emit(line.rstrip())
        finally:
            if fd is not None:
                os.close(fd)

    def is_current(self, fd):
        try:
            ino = os.stat(self._path).st_ino
        except OSError:
            return False
        else:
            return ino == os.fstat(fd).st_ino

    def readsome(self, fd):
        while 1:
            try:
                return os.read(fd, 1024**2)
            except OSError as e:
                if e.errno == errno.EAGAIN:
                    gevent.sleep(0.2)
                    continue
                raise
示例#7
0
class BaseBuffer(Configurable):

    buffer_chunk_limit = config_param('size', 128*1024*1024)
    buffer_queue_limit = config_param('integer', 128)
    flush_interval = config_param('time', 60)

    _shutdown = False

    def new_chunk(self, key, expire):
        raise NotImplemented

    def start(self):
        self._queue = gevent.queue.Queue(self.buffer_queue_limit)
        self._map = {}
        gevent.spawn(self.run)

    def run(self):
        # flush を実行する間隔
        check_interval = min(self.flush_interval/2, 2)
        while not self._shutdown:
            gevent.sleep(check_interval)
            self.flush(force=False)

    def emit(self, key, data):
        top = self._map.get(key)
        if not top:
            top = self._map[key] = self.new_chunk(key, now()+self.flush_interval)

        if len(top) + len(data) <= self.buffer_chunk_limit:
            top += data
            return False

        if len(data) > self.buffer_chunk_limit:
            log.warn("Size of the emitted data exceeds buffer_chunk_limit.\n"
                     "This may occur problems in the output plugins ``at this server.``\n"
                     "To avoid problems, set a smaller number to the buffer_chunk_limit\n"
                     "in the forward output ``at the log forwarding server.``"
                     )

        nc = self.new_chunk(key, now()+self.flush_interval)

        try:
            nc += data
            self._map[key] = nc
            self._queue.put_nowait(top)
        except gqueue.Full:
            log.error("buffer_queue_limit is exceeded.")
        except:
            nc.purge()
            raise

    def keys(self):
        return self._map.keys()

    def flush(self, force=True):
        u"""バッファリング中のチャンクを書きこみ待ちキューへ移動する.
        *force* が true のときはすべてのチャンクを移動し、 false の時は
        chunk.expire が古いものだけをフラッシュする.
        """
        map_ = self._map
        keys = list(map_.keys())
        t = now()
        for key in keys:
            chunk = map_[key]
            if not force and chunk.expire > t:
                continue
            del map_[key]
            self._queue.put(chunk) # Would block here.
            gevent.sleep(0) # give a chance to write.
        if keys:
            log.debug("flush: queue size=%s", self._queue.qsize())

    def get(self, block=True, timeout=None):
        return self._queue.get(block, timeout)

    def get_nowait(self):
        return self._queue.get_nowait()

    def shutdown(self):
        self._shutdown = True
        self.flush()
示例#8
0
class BufferedOutput(Output):

    _shutdown = False

    def __init__(self):
        super(BufferedOutput, self).__init__()
        self._last_retry_time = 0
        self._next_retry_time = 0
        self._secondary_limit = 8
        self._emit_count = 0

    buffer_type = config_param('string', 'memory')
    retry_limit = config_param('integer', 17)
    retry_wait = config_param('time', 1.0)

    def configure(self, conf):
        super(BufferedOutput, self).configure(conf)

        self._buffer = Plugin.new_buffer(self.buffer_type)
        self._buffer.configure(conf)
        #todo: secondary
        #todo: status

    def start(self):
        self._buffer.start()
        super(BufferedOutput, self).start()
        #todo: secondary.start()
        gevent.spawn(self.run)

    def run(self):
        while not self._shutdown:
            try:
                chunk = self._buffer.get(timeout=1.0)
            except gevent.queue.Empty:
                continue
            retry = 0
            retry_wait = self.retry_wait
            while retry <= self.retry_limit:
                try:
                    self.write(chunk)
                    break
                except Exception as e:
                    log.warn("fail to write: %r", e)
                    gevent.sleep(retry_wait)
                    retry_wait *= 2
        while 1:
            try:
                chunk = self._buffer.get_nowait()
                self.write(chunk.tag, chunk.read())
            except gevent.queue.Empty:
                break

    def shutdown(self):
        self._buffer.shutdown()
        self._shutdown = True
        super(BufferedOutput, self).shutdown()

    def emit(self, tag, es, key=''):
        self._emit_count += 1
        data = self.format_stream(tag, es)
        self._buffer.emit(key, data)

    def write(self, chunk):
        raise NotImplemented

    def format(self, tag, time, record):
        return "%s\t%s\t%s\n" % (time, tag, json.dumps(record))

    def format_stream(self, tag, es):
        buf = BytesIO()
        format = self.format
        for time, record in es:
            buf.write(format(tag, time, record))
        return buf.getvalue()