mercredi 2 mai 2018

How to retype a function in Golang?

In my current Golang project, I use buffering of logs before sending them to Elasticsearch within my log library. I want to introduce something like C atexit() function to flush all pending logs in case of an unexpected exit.

I have found atexit library but it was insufficient in my case because it does not allow passing of arguments to handler functions. I decided to write my version and ended up with roughly similar structure. I have module atexit:

package atexit

import (
    "fmt"
    "os"
    "reflect"
)

var handlers []interface{}
var params []interface{}

func runHandler(handler interface{}, p interface{}) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Fprintln(os.Stderr, "error: atexit handler error:", err)
        }
    }()

    f := reflect.ValueOf(handler)
    s := reflectSlice(p)
    fmt.Printf("%#v", s)
    f.Call(s)
}

func reflectSlice(slice interface{}) []reflect.Value {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("InterfaceSlice() given a non-slice type")
    }

    ret := make([]reflect.Value, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = reflect.ValueOf(s.Index(i))
    }

    return ret
}

func runHandlers() {
    for i, handler := range handlers {
        runHandler(handler, params[i])
    }
}

func Exit(code int) {
    runHandlers()
    os.Exit(code)
}

func Register(handler interface{}, p interface{}) {
    f := reflect.TypeOf(handler)
    if f.Kind() != reflect.Func {
        panic("Register() given a non-function type")
    }
    handlers = append(handlers, handler)
    params = append(params, p)
}

and I am calling it from the main program:

package main
import (
    "fmt"
    "./atexit"
    "encoding/json"
    "reflect"
)

type Batch struct {
    Index string        `json:"index"`
    Type string     `json:"_Type"`
    Content interface{} `json:"Content"`
}

func flush(b ...interface{}) {
    for _, entry := range(b) {
        fmt.Printf("%#v\n", entry)
        fmt.Println(reflect.TypeOf(reflect.ValueOf(entry)))
        fmt.Println(reflect.TypeOf(entry))
        a, err  := json.Marshal(entry)
        if err != nil {
            fmt.Println("error!")
        }
        fmt.Println(string(a))
    }
}

func handler(v ...interface{}) {
    fmt.Println("Exiting")
    for _, batch := range(v) {
        fmt.Printf("%#v\n", batch)
    }
}

func main() {
    type ColorGroup struct {
        ID     int
        Name   string
        Colors []string
    }
    group := ColorGroup{
        ID:     1,
        Name:   "Reds",
        Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
    }
    fmt.Printf("%#v\n", group)
    r, err := json.Marshal(group)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Println(string(r))


    b := []Batch{Batch{"index", "type", "content1"},Batch{"index", "type", "content2"}}
    atexit.Register(handler, b)
    atexit.Register(flush, []ColorGroup{group})
    atexit.Exit(0)
}

As you can see, by calling reflect.ValueOf() I get structure reflect.Value which is then passed to callback function. The problem seems to be that this structure does not contain metadata about json export or is not handled correctly with json.Marshal(), which then outputs empty json. Is there any way I can pass correct []interface{} structure to the callback function or some similar mechanism which would do roughly what I'm trying to accomplish? Please note that I want a general callback mechanism which should be independent of the type passed to it. So far it seems to be impossible or at least very limited by f.Call(s), which is called in runHandler() and takes reflect.Value as its parameter.





Aucun commentaire:

Enregistrer un commentaire