renderer.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package renderer
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "time"
  11. "strconv"
  12. "strings"
  13. "github.com/grafana/grafana/pkg/log"
  14. "github.com/grafana/grafana/pkg/middleware"
  15. "github.com/grafana/grafana/pkg/models"
  16. "github.com/grafana/grafana/pkg/setting"
  17. "github.com/grafana/grafana/pkg/util"
  18. )
  19. type RenderOpts struct {
  20. Path string
  21. Width string
  22. Height string
  23. Timeout string
  24. OrgId int64
  25. UserId int64
  26. OrgRole models.RoleType
  27. Timezone string
  28. IsAlertContext bool
  29. Encoding string
  30. }
  31. var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
  32. var rendererLog log.Logger = log.New("png-renderer")
  33. func isoTimeOffsetToPosixTz(isoOffset string) string {
  34. // invert offset
  35. if strings.HasPrefix(isoOffset, "UTC+") {
  36. return strings.Replace(isoOffset, "UTC+", "UTC-", 1)
  37. }
  38. if strings.HasPrefix(isoOffset, "UTC-") {
  39. return strings.Replace(isoOffset, "UTC-", "UTC+", 1)
  40. }
  41. return isoOffset
  42. }
  43. func appendEnviron(baseEnviron []string, name string, value string) []string {
  44. results := make([]string, 0)
  45. prefix := fmt.Sprintf("%s=", name)
  46. for _, v := range baseEnviron {
  47. if !strings.HasPrefix(v, prefix) {
  48. results = append(results, v)
  49. }
  50. }
  51. return append(results, fmt.Sprintf("%s=%s", name, value))
  52. }
  53. func RenderToPng(params *RenderOpts) (string, error) {
  54. rendererLog.Info("Rendering", "path", params.Path)
  55. var executable = "phantomjs"
  56. if runtime.GOOS == "windows" {
  57. executable = executable + ".exe"
  58. }
  59. localDomain := "localhost"
  60. if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
  61. localDomain = setting.HttpAddr
  62. }
  63. url := fmt.Sprintf("%s://%s:%s/%s", setting.Protocol, localDomain, setting.HttpPort, params.Path)
  64. binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
  65. scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
  66. pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
  67. pngPath = pngPath + ".png"
  68. orgRole := params.OrgRole
  69. if params.IsAlertContext {
  70. orgRole = models.ROLE_ADMIN
  71. }
  72. renderKey := middleware.AddRenderAuthKey(params.OrgId, params.UserId, orgRole)
  73. defer middleware.RemoveRenderAuthKey(renderKey)
  74. timeout, err := strconv.Atoi(params.Timeout)
  75. if err != nil {
  76. timeout = 15
  77. }
  78. phantomDebugArg := "--debug=false"
  79. if log.GetLogLevelFor("png-renderer") >= log.LvlDebug {
  80. phantomDebugArg = "--debug=true"
  81. }
  82. cmdArgs := []string{
  83. "--ignore-ssl-errors=true",
  84. "--web-security=false",
  85. phantomDebugArg,
  86. scriptPath,
  87. "url=" + url,
  88. "width=" + params.Width,
  89. "height=" + params.Height,
  90. "png=" + pngPath,
  91. "domain=" + localDomain,
  92. "timeout=" + strconv.Itoa(timeout),
  93. "renderKey=" + renderKey,
  94. }
  95. if params.Encoding != "" {
  96. cmdArgs = append([]string{fmt.Sprintf("--output-encoding=%s", params.Encoding)}, cmdArgs...)
  97. }
  98. cmd := exec.Command(binPath, cmdArgs...)
  99. output, err := cmd.StdoutPipe()
  100. if err != nil {
  101. rendererLog.Error("Could not acquire stdout pipe", err)
  102. return "", err
  103. }
  104. cmd.Stderr = cmd.Stdout
  105. if params.Timezone != "" {
  106. baseEnviron := os.Environ()
  107. cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(params.Timezone))
  108. }
  109. err = cmd.Start()
  110. if err != nil {
  111. rendererLog.Error("Could not start command", err)
  112. return "", err
  113. }
  114. logWriter := log.NewLogWriter(rendererLog, log.LvlDebug, "[phantom] ")
  115. go io.Copy(logWriter, output)
  116. done := make(chan error)
  117. go func() {
  118. if err := cmd.Wait(); err != nil {
  119. rendererLog.Error("failed to render an image", "error", err)
  120. }
  121. close(done)
  122. }()
  123. select {
  124. case <-time.After(time.Duration(timeout) * time.Second):
  125. if err := cmd.Process.Kill(); err != nil {
  126. rendererLog.Error("failed to kill", "error", err)
  127. }
  128. return "", ErrTimeout
  129. case <-done:
  130. }
  131. rendererLog.Debug("Image rendered", "path", pngPath)
  132. return pngPath, nil
  133. }