1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-03 15:02:32 +02:00

#27 Added logging

This commit is contained in:
Tim Voronov 2018-09-28 00:28:33 -04:00
parent 4d4c6ceadd
commit e427efd74e
18 changed files with 408 additions and 112 deletions

1
.gitignore vendored
View File

@ -124,3 +124,4 @@ $RECYCLE.BIN/
vendor vendor
bin bin
*.log

31
Gopkg.lock generated
View File

@ -41,6 +41,14 @@
pruneopts = "UT" pruneopts = "UT"
revision = "2b8494104d86337cdd41d0a49cbed8e4583c0ab4" revision = "2b8494104d86337cdd41d0a49cbed8e4583c0ab4"
[[projects]]
digest = "1:ce579162ae1341f3e5ab30c0dce767f28b1eb6a81359aad01723f1ba6b4becdf"
name = "github.com/gofrs/uuid"
packages = ["."]
pruneopts = "UT"
revision = "370558f003bfe29580cd0f698d8640daccdcc45c"
version = "v3.1.1"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f14d1b50e0075fb00177f12a96dd7addf93d1e2883c25befd17285b779549795" digest = "1:f14d1b50e0075fb00177f12a96dd7addf93d1e2883c25befd17285b779549795"
@ -121,6 +129,14 @@
revision = "75b0ecc5efcff27ac756a33ec71f0db75dc3d21c" revision = "75b0ecc5efcff27ac756a33ec71f0db75dc3d21c"
version = "v0.19.0" version = "v0.19.0"
[[projects]]
digest = "1:c805e517269b0ba4c21ded5836019ed7d16953d4026cb7d00041d039c7906be9"
name = "github.com/natefinch/lumberjack"
packages = ["."]
pruneopts = "UT"
revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
version = "v2.1"
[[projects]] [[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors" name = "github.com/pkg/errors"
@ -129,6 +145,18 @@
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0" version = "v0.8.0"
[[projects]]
digest = "1:5bc8f93f977b72a7a5264725c3bab275e69de0cc3e5c0dc1ee56feb564c33f03"
name = "github.com/rs/zerolog"
packages = [
".",
"internal/cbor",
"internal/json",
]
pruneopts = "UT"
revision = "338f9bc14084d22cb8eeacd6492861f8449d715c"
version = "v1.9.1"
[[projects]] [[projects]]
digest = "1:4ca145a665316d3c020a39c0741780fa3636b9152b824206796c4dce541f4a24" digest = "1:4ca145a665316d3c020a39c0741780fa3636b9152b824206796c4dce541f4a24"
name = "github.com/sethgrid/pester" name = "github.com/sethgrid/pester"
@ -189,6 +217,7 @@
"github.com/antlr/antlr4/runtime/Go/antlr", "github.com/antlr/antlr4/runtime/Go/antlr",
"github.com/chzyer/readline", "github.com/chzyer/readline",
"github.com/corpix/uarand", "github.com/corpix/uarand",
"github.com/gofrs/uuid",
"github.com/mafredri/cdp", "github.com/mafredri/cdp",
"github.com/mafredri/cdp/devtool", "github.com/mafredri/cdp/devtool",
"github.com/mafredri/cdp/protocol/dom", "github.com/mafredri/cdp/protocol/dom",
@ -198,7 +227,9 @@
"github.com/mafredri/cdp/protocol/target", "github.com/mafredri/cdp/protocol/target",
"github.com/mafredri/cdp/rpcc", "github.com/mafredri/cdp/rpcc",
"github.com/mafredri/cdp/session", "github.com/mafredri/cdp/session",
"github.com/natefinch/lumberjack",
"github.com/pkg/errors", "github.com/pkg/errors",
"github.com/rs/zerolog",
"github.com/sethgrid/pester", "github.com/sethgrid/pester",
"github.com/smartystreets/goconvey/convey", "github.com/smartystreets/goconvey/convey",
"golang.org/x/net/html", "golang.org/x/net/html",

View File

@ -42,5 +42,9 @@
version = "1.4" version = "1.4"
[[constraint]] [[constraint]]
name = "github.com/PuerkitoBio/goquery" name = "github.com/PuerkitoBio/goquery"
version = "1.4.1" version = "1.4.1"
[[constraint]]
name = "github.com/gofrs/uuid"
version = "3.1.1"

View File

@ -5,8 +5,11 @@ import (
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal"
"syscall"
) )
func ExecFile(pathToFile string, opts Options) { func ExecFile(pathToFile string, opts Options) {
@ -33,9 +36,25 @@ func Exec(query string, opts Options) {
return return
} }
l := NewLogger()
ctx, cancel := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for {
<-c
cancel()
l.Close()
}
}()
out, err := prog.Run( out, err := prog.Run(
context.Background(), ctx,
runtime.WithBrowser(opts.Cdp), runtime.WithBrowser(opts.Cdp),
runtime.WithLog(l),
runtime.WithLogLevel(logging.DebugLevel),
) )
if err != nil { if err != nil {

14
cmd/cli/logger.go Normal file
View File

@ -0,0 +1,14 @@
package cli
import (
"github.com/natefinch/lumberjack"
)
func NewLogger() *lumberjack.Logger {
l := &lumberjack.Logger{
Filename: "./ferret.log",
MaxSize: 100,
}
return l
}

View File

@ -5,8 +5,12 @@ import (
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"github.com/chzyer/readline" "github.com/chzyer/readline"
"os"
"os/signal"
"strings" "strings"
"syscall"
) )
func Repl(version string, opts Options) { func Repl(version string, opts Options) {
@ -32,6 +36,20 @@ func Repl(version string, opts Options) {
timer := NewTimer() timer := NewTimer()
l := NewLogger()
ctx, cancel := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for {
<-c
cancel()
l.Close()
}
}()
for { for {
line, err := rl.Readline() line, err := rl.Readline()
@ -75,8 +93,10 @@ func Repl(version string, opts Options) {
timer.Start() timer.Start()
out, err := program.Run( out, err := program.Run(
context.Background(), ctx,
runtime.WithBrowser(opts.Cdp), runtime.WithBrowser(opts.Cdp),
runtime.WithLog(l),
runtime.WithLogLevel(logging.DebugLevel),
) )
timer.Stop() timer.Stop()

View File

@ -27,7 +27,7 @@ var (
conn = flag.String( conn = flag.String(
"cdp", "cdp",
"http://127.0.0.1:9222", "http://0.0.0.0:9222",
"set CDP address", "set CDP address",
) )

View File

@ -1,9 +0,0 @@
LET g = DOCUMENT("https://www.google.com/", true)
INPUT(ELEMENT(g, 'input[name="q"]'), "ferret")
CLICK(g, 'input[name="btnK"]')
WAIT_NAVIGATION(g)
RETURN 1

View File

@ -78,7 +78,7 @@ func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error
} }
}() }()
l := newVisitor(c.funcs) l := newVisitor(query, c.funcs)
res := p.Visit(l).(*result) res := p.Visit(l).(*result)

View File

@ -1688,39 +1688,24 @@ func TestForTernaryExpression(t *testing.T) {
}) })
} }
//func TestHtml(t *testing.T) { func TestHtml(t *testing.T) {
// Convey("Should load a document", t, func() { Convey("Should load a document", t, func() {
// c := compiler.New() c := compiler.New()
//
// prog, err := c.Compile(` out, err := c.MustCompile(`
//LET doc = DOCUMENT('https://soundcloud.com/charts/top', true) LET doc = DOCUMENT("https://github.com/", true)
// LET btn = ELEMENT(doc, ".HeaderMenu a")
//// TODO: We need a better way of waiting for page loading
//// Something line WAIT_FOR(doc, selector) CLICK(btn)
//SLEEP(2000) WAIT_NAVIGATION(doc)
// WAIT_ELEMENT(doc, '.IconNav')
//LET tracks = ELEMENTS(doc, '.chartTrack__details')
// RETURN INNER_HTML_ALL(doc, '.IconNav a')
//LOG("found", LENGTH(tracks), "tracks")
// `).Run(context.Background())
//FOR track IN tracks
// // LET username = ELEMENT(track, '.chartTrack__username') So(err, ShouldBeNil)
// // LET title = ELEMENT(track, '.chartTrack__title')
// So(string(out), ShouldEqual, `"int"`)
// // LOG("NODE", track.nodeName) })
// }
// SLEEP(500)
//
// RETURN track.innerHtml
//
// `)
//
// So(err, ShouldBeNil)
//
// out, err := prog.Run(context.Background(), runtime.WithBrowser("http://127.0.0.1:9222"))
//
// So(err, ShouldBeNil)
//
// So(string(out), ShouldEqual, `"int"`)
// })
//}

View File

@ -18,13 +18,14 @@ import (
type visitor struct { type visitor struct {
*fql.BaseFqlParserVisitor *fql.BaseFqlParserVisitor
src string
funcs map[string]core.Function funcs map[string]core.Function
} }
func newVisitor(funcs map[string]core.Function) *visitor { func newVisitor(src string, funcs map[string]core.Function) *visitor {
return &visitor{ return &visitor{
&fql.BaseFqlParserVisitor{}, &fql.BaseFqlParserVisitor{},
src,
funcs, funcs,
} }
} }
@ -38,7 +39,7 @@ func (v *visitor) VisitProgram(ctx *fql.ProgramContext) interface{} {
return nil, err return nil, err
} }
return runtime.NewProgram(block), nil return runtime.NewProgram(v.src, block)
}) })
} }

View File

@ -0,0 +1,23 @@
package logging
import (
"context"
"github.com/rs/zerolog"
)
type Level uint8
const (
DebugLevel Level = iota
InfoLevel
WarnLevel
ErrorLevel
FatalLevel
PanicLevel
NoLevel
Disabled
)
func From(ctx context.Context) *zerolog.Logger {
return zerolog.Ctx(ctx)
}

View File

@ -1,12 +1,21 @@
package runtime package runtime
import "context" import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"github.com/gofrs/uuid"
"github.com/rs/zerolog"
"io"
"os"
)
type ( type (
Options struct { Options struct {
proxy string proxy string
cdp string cdp string
variables map[string]interface{} variables map[string]interface{}
logWriter io.Writer
logLevel zerolog.Level
} }
Option func(*Options) Option func(*Options)
@ -14,8 +23,10 @@ type (
func newOptions() *Options { func newOptions() *Options {
return &Options{ return &Options{
cdp: "http://127.0.0.1:9222", cdp: "http://0.0.0.0:9222",
variables: make(map[string]interface{}), variables: make(map[string]interface{}),
logWriter: os.Stdout,
logLevel: zerolog.ErrorLevel,
} }
} }
@ -38,10 +49,38 @@ func WithProxy(address string) Option {
} }
} }
func WithLog(writer io.Writer) Option {
return func(options *Options) {
options.logWriter = writer
}
}
func WithLogLevel(lvl logging.Level) Option {
return func(options *Options) {
options.logLevel = zerolog.Level(lvl)
}
}
func (opts *Options) withContext(parent context.Context) context.Context { func (opts *Options) withContext(parent context.Context) context.Context {
return context.WithValue( ctx := context.WithValue(
parent, parent,
"variables", "variables",
opts.variables, opts.variables,
) )
id, err := uuid.NewV4()
if err != nil {
panic(err)
}
logger := zerolog.New(opts.logWriter).
With().
Str("id", id.String()).
Logger()
logger.WithLevel(opts.logLevel)
ctx = logger.WithContext(ctx)
return ctx
} }

View File

@ -8,11 +8,24 @@ import (
) )
type Program struct { type Program struct {
exp core.Expression src string
body core.Expression
} }
func NewProgram(exp core.Expression) *Program { func NewProgram(src string, body core.Expression) (*Program, error) {
return &Program{exp} if src == "" {
return nil, core.Error(core.ErrMissedArgument, "source")
}
if core.IsNil(body) {
return nil, core.Error(core.ErrMissedArgument, "body")
}
return &Program{src, body}, nil
}
func (p *Program) Source() string {
return p.src
} }
func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) { func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) {
@ -30,7 +43,7 @@ func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) {
ctx = driver.WithDynamicDriver(ctx, opts.cdp) ctx = driver.WithDynamicDriver(ctx, opts.cdp)
ctx = driver.WithStaticDriver(ctx) ctx = driver.WithStaticDriver(ctx)
out, err := p.exp.Exec(ctx, scope) out, err := p.body.Exec(ctx, scope)
if err != nil { if err != nil {
js, _ := values.None.MarshalJSON() js, _ := values.None.MarshalJSON()

View File

@ -5,6 +5,7 @@ import (
"crypto/sha512" "crypto/sha512"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"github.com/MontFerret/ferret/pkg/runtime/values" "github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic/eval" "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic/eval"
"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic/events" "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic/events"
@ -15,12 +16,14 @@ import (
"github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/protocol/page"
"github.com/mafredri/cdp/rpcc" "github.com/mafredri/cdp/rpcc"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog"
"sync" "sync"
"time" "time"
) )
type HtmlDocument struct { type HtmlDocument struct {
sync.Mutex sync.Mutex
logger *zerolog.Logger
conn *rpcc.Conn conn *rpcc.Conn
client *cdp.Client client *cdp.Client
events *events.EventBroker events *events.EventBroker
@ -93,7 +96,14 @@ func LoadHtmlDocument(
return nil, err return nil, err
} }
return NewHtmlDocument(conn, client, broker, root, innerHtml), nil return NewHtmlDocument(
logging.From(ctx),
conn,
client,
broker,
root,
innerHtml,
), nil
} }
func getRootElement(client *cdp.Client) (dom.Node, values.String, error) { func getRootElement(client *cdp.Client) (dom.Node, values.String, error) {
@ -117,6 +127,7 @@ func getRootElement(client *cdp.Client) (dom.Node, values.String, error) {
} }
func NewHtmlDocument( func NewHtmlDocument(
logger *zerolog.Logger,
conn *rpcc.Conn, conn *rpcc.Conn,
client *cdp.Client, client *cdp.Client,
broker *events.EventBroker, broker *events.EventBroker,
@ -124,10 +135,11 @@ func NewHtmlDocument(
innerHtml values.String, innerHtml values.String,
) *HtmlDocument { ) *HtmlDocument {
doc := new(HtmlDocument) doc := new(HtmlDocument)
doc.logger = logger
doc.conn = conn doc.conn = conn
doc.client = client doc.client = client
doc.events = broker doc.events = broker
doc.element = NewHtmlElement(client, broker, root.NodeID, root, innerHtml) doc.element = NewHtmlElement(doc.logger, client, broker, root.NodeID, root, innerHtml)
doc.url = "" doc.url = ""
if root.BaseURL != nil { if root.BaseURL != nil {
@ -141,7 +153,11 @@ func NewHtmlDocument(
updated, innerHtml, err := getRootElement(client) updated, innerHtml, err := getRootElement(client)
if err != nil { if err != nil {
// TODO: We need somehow log all errors outside of stdout doc.logger.Error().
Timestamp().
Err(err).
Msg("failed to get root node after page load")
return return
} }
@ -149,7 +165,7 @@ func NewHtmlDocument(
doc.element.Close() doc.element.Close()
// create a new root element wrapper // create a new root element wrapper
doc.element = NewHtmlElement(client, broker, updated.NodeID, updated, innerHtml) doc.element = NewHtmlElement(doc.logger, client, broker, updated.NodeID, updated, innerHtml)
doc.url = "" doc.url = ""
if updated.BaseURL != nil { if updated.BaseURL != nil {
@ -226,11 +242,47 @@ func (doc *HtmlDocument) Close() error {
doc.Lock() doc.Lock()
defer doc.Unlock() defer doc.Unlock()
doc.events.Stop() var err error
doc.events.Close()
doc.element.Close() err = doc.events.Stop()
doc.client.Page.Close(context.Background())
if err != nil {
doc.logger.Warn().
Timestamp().
Str("url", doc.url.String()).
Err(err).
Msg("failed to stop event broker")
}
err = doc.events.Close()
if err != nil {
doc.logger.Warn().
Timestamp().
Str("url", doc.url.String()).
Err(err).
Msg("failed to close event broker")
}
err = doc.element.Close()
if err != nil {
doc.logger.Warn().
Timestamp().
Str("url", doc.url.String()).
Err(err).
Msg("failed to close root element")
}
err = doc.client.Page.Close(context.Background())
if err != nil {
doc.logger.Warn().
Timestamp().
Str("url", doc.url.String()).
Err(err).
Msg("failed to close browser page")
}
return doc.conn.Close() return doc.conn.Close()
} }
@ -354,13 +406,18 @@ func (doc *HtmlDocument) InnerHtmlBySelectorAll(selector values.String) (*values
res, err := eval.Eval( res, err := eval.Eval(
doc.client, doc.client,
fmt.Sprintf(` fmt.Sprintf(`
var result = [];
var elements = document.querySelectorAll(%s); var elements = document.querySelectorAll(%s);
if (elements == null) { if (elements == null) {
return []; return result;
} }
return elements.map(i => i.innerHtml); elements.forEach((i) => {
result.push(i.innerHtml);
});
return result;
`, eval.ParamString(selector.String())), `, eval.ParamString(selector.String())),
true, true,
false, false,
@ -408,13 +465,18 @@ func (doc *HtmlDocument) InnerTextBySelectorAll(selector values.String) (*values
res, err := eval.Eval( res, err := eval.Eval(
doc.client, doc.client,
fmt.Sprintf(` fmt.Sprintf(`
var result = [];
var elements = document.querySelectorAll(%s); var elements = document.querySelectorAll(%s);
if (elements == null) { if (elements == null) {
return []; return result;
} }
return elements.map(i => i.innerText); elements.forEach((i) => {
result.push(i.innerText);
});
return result;
`, eval.ParamString(selector.String())), `, eval.ParamString(selector.String())),
true, true,
false, false,

View File

@ -13,6 +13,7 @@ import (
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/mafredri/cdp" "github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/dom"
"github.com/rs/zerolog"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -22,6 +23,7 @@ const DefaultTimeout = time.Second * 30
type HtmlElement struct { type HtmlElement struct {
sync.Mutex sync.Mutex
logger *zerolog.Logger
client *cdp.Client client *cdp.Client
broker *events.EventBroker broker *events.EventBroker
connected values.Boolean connected values.Boolean
@ -31,12 +33,14 @@ type HtmlElement struct {
innerHtml values.String innerHtml values.String
innerText *common.LazyValue innerText *common.LazyValue
value core.Value value core.Value
rawAttrs []string
attributes *common.LazyValue attributes *common.LazyValue
children []dom.NodeID children []dom.NodeID
loadedChildren *common.LazyValue loadedChildren *common.LazyValue
} }
func LoadElement( func LoadElement(
logger *zerolog.Logger,
client *cdp.Client, client *cdp.Client,
broker *events.EventBroker, broker *events.EventBroker,
id dom.NodeID, id dom.NodeID,
@ -68,6 +72,7 @@ func LoadElement(
} }
return NewHtmlElement( return NewHtmlElement(
logger,
client, client,
broker, broker,
id, id,
@ -77,6 +82,7 @@ func LoadElement(
} }
func NewHtmlElement( func NewHtmlElement(
logger *zerolog.Logger,
client *cdp.Client, client *cdp.Client,
broker *events.EventBroker, broker *events.EventBroker,
id dom.NodeID, id dom.NodeID,
@ -84,6 +90,7 @@ func NewHtmlElement(
innerHtml values.String, innerHtml values.String,
) *HtmlElement { ) *HtmlElement {
el := new(HtmlElement) el := new(HtmlElement)
el.logger = logger
el.client = client el.client = client
el.broker = broker el.broker = broker
el.connected = values.True el.connected = values.True
@ -91,34 +98,11 @@ func NewHtmlElement(
el.nodeType = values.NewInt(node.NodeType) el.nodeType = values.NewInt(node.NodeType)
el.nodeName = values.NewString(node.NodeName) el.nodeName = values.NewString(node.NodeName)
el.innerHtml = innerHtml el.innerHtml = innerHtml
el.innerText = common.NewLazyValue(func() (core.Value, error) { el.innerText = common.NewLazyValue(el.loadInnerText)
h := el.InnerHtml() el.rawAttrs = node.Attributes[:]
el.attributes = common.NewLazyValue(el.loadAttrs)
if h == values.EmptyString {
return h, nil
}
buff := bytes.NewBuffer([]byte(h))
parsed, err := goquery.NewDocumentFromReader(buff)
if err != nil {
return values.EmptyString, err
}
return values.NewString(parsed.Text()), nil
})
el.attributes = common.NewLazyValue(func() (core.Value, error) {
return parseAttrs(node.Attributes), nil
})
el.value = values.EmptyString el.value = values.EmptyString
el.loadedChildren = common.NewLazyValue(func() (core.Value, error) { el.loadedChildren = common.NewLazyValue(el.loadChildren)
if !el.IsConnected() {
return values.NewArray(0), nil
}
return loadNodes(client, broker, el.children)
})
if node.Value != nil { if node.Value != nil {
el.value = values.NewString(*node.Value) el.value = values.NewString(*node.Value)
@ -205,11 +189,19 @@ func (el *HtmlElement) Unwrap() interface{} {
} }
func (el *HtmlElement) Hash() int { func (el *HtmlElement) Hash() int {
el.Lock()
defer el.Unlock()
h := sha512.New() h := sha512.New()
out, err := h.Write([]byte(strconv.Itoa(int(el.id)))) out, err := h.Write([]byte(el.innerHtml))
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Msg("failed to calculate hash value")
return 0 return 0
} }
@ -227,6 +219,11 @@ func (el *HtmlElement) Value() core.Value {
val, err := eval.Property(ctx, el.client, el.id, "value") val, err := eval.Property(ctx, el.client, el.id, "value")
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Msg("failed to get node value")
return el.value return el.value
} }
@ -309,12 +306,24 @@ func (el *HtmlElement) QuerySelector(selector values.String) core.Value {
found, err := el.client.DOM.QuerySelector(ctx, selectorArgs) found, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Str("selector", selector.String()).
Err(err).
Msg("failed to retrieve a node by selector")
return values.None return values.None
} }
res, err := LoadElement(el.client, el.broker, found.NodeID) res, err := LoadElement(el.logger, el.client, el.broker, found.NodeID)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Str("selector", selector.String()).
Err(err).
Msg("failed to load a child node by selector")
return values.None return values.None
} }
@ -332,15 +341,27 @@ func (el *HtmlElement) QuerySelectorAll(selector values.String) core.Value {
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs) res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Str("selector", selector.String()).
Err(err).
Msg("failed to retrieve nodes by selector")
return values.None return values.None
} }
arr := values.NewArray(len(res.NodeIDs)) arr := values.NewArray(len(res.NodeIDs))
for _, id := range res.NodeIDs { for _, id := range res.NodeIDs {
childEl, err := LoadElement(el.client, el.broker, id) childEl, err := LoadElement(el.logger, el.client, el.broker, id)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Str("selector", selector.String()).
Err(err).
Msg("failed to load nodes by selector")
return values.None return values.None
} }
@ -389,6 +410,54 @@ func (el *HtmlElement) IsConnected() values.Boolean {
return el.connected return el.connected
} }
func (el *HtmlElement) loadInnerText() (core.Value, error) {
h := el.InnerHtml()
if h == values.EmptyString {
return h, nil
}
buff := bytes.NewBuffer([]byte(h))
parsed, err := goquery.NewDocumentFromReader(buff)
if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to parse inner html")
return values.EmptyString, err
}
return values.NewString(parsed.Text()), nil
}
func (el *HtmlElement) loadAttrs() (core.Value, error) {
return parseAttrs(el.rawAttrs), nil
}
func (el *HtmlElement) loadChildren() (core.Value, error) {
if !el.IsConnected() {
return values.NewArray(0), nil
}
loaded, err := loadNodes(el.logger, el.client, el.broker, el.children)
if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to load child nodes")
return values.None, err
}
return loaded, nil
}
func (el *HtmlElement) handlePageReload(message interface{}) { func (el *HtmlElement) handlePageReload(message interface{}) {
el.Close() el.Close()
} }
@ -484,6 +553,12 @@ func (el *HtmlElement) handleChildrenCountChanged(message interface{}) {
node, err := el.client.DOM.DescribeNode(context.Background(), dom.NewDescribeNodeArgs()) node, err := el.client.DOM.DescribeNode(context.Background(), dom.NewDescribeNodeArgs())
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to update node")
return return
} }
@ -536,10 +611,15 @@ func (el *HtmlElement) handleChildInserted(message interface{}) {
} }
loadedArr := loaded.(*values.Array) loadedArr := loaded.(*values.Array)
loadedEl, err := LoadElement(el.logger, el.client, el.broker, nextId)
loadedEl, err := LoadElement(el.client, el.broker, nextId)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to load an inserted node")
return return
} }
@ -548,6 +628,12 @@ func (el *HtmlElement) handleChildInserted(message interface{}) {
newInnerHtml, err := loadInnerHtml(el.client, el.id) newInnerHtml, err := loadInnerHtml(el.client, el.id)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to update node")
return return
} }
@ -601,6 +687,12 @@ func (el *HtmlElement) handleChildDeleted(message interface{}) {
newInnerHtml, err := loadInnerHtml(el.client, el.id) newInnerHtml, err := loadInnerHtml(el.client, el.id)
if err != nil { if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("id", int(el.id)).
Msg("failed to update node")
return return
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/mafredri/cdp" "github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/protocol/page"
"github.com/rs/zerolog"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -70,11 +71,11 @@ func createChildrenArray(nodes []dom.Node) []dom.NodeID {
return children return children
} }
func loadNodes(client *cdp.Client, broker *events.EventBroker, nodes []dom.NodeID) (*values.Array, error) { func loadNodes(logger *zerolog.Logger, client *cdp.Client, broker *events.EventBroker, nodes []dom.NodeID) (*values.Array, error) {
arr := values.NewArray(len(nodes)) arr := values.NewArray(len(nodes))
for _, id := range nodes { for _, id := range nodes {
child, err := LoadElement(client, broker, id) child, err := LoadElement(logger, client, broker, id)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -3,8 +3,8 @@ package utils
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"github.com/MontFerret/ferret/pkg/runtime/values" "github.com/MontFerret/ferret/pkg/runtime/values"
"log"
"time" "time"
) )
@ -30,16 +30,16 @@ func Wait(_ context.Context, inputs ...core.Value) (core.Value, error) {
return values.None, nil return values.None, nil
} }
func Log(_ context.Context, inputs ...core.Value) (core.Value, error) { func Log(ctx context.Context, inputs ...core.Value) (core.Value, error) {
args := make([]interface{}, 0, len(inputs)+1) args := make([]interface{}, 0, len(inputs)+1)
args = append(args, "LOG:")
for _, input := range inputs { for _, input := range inputs {
args = append(args, input) args = append(args, input)
} }
log.Println(args...) logger := logging.From(ctx)
logger.Print(args...)
return values.None, nil return values.None, nil
} }