New Provider: OpsGenie (#11012)
* Importing the OpsGenie SDK * Adding the goreq dependency * Initial commit of the OpsGenie / User provider * Refactoring to return a single client * Adding an import test / fixing a copy/paste error * Adding support for OpsGenie docs * Scaffolding the user documentation for OpsGenie * Adding a TODO * Adding the User data source * Documentation for OpsGenie * Adding OpsGenie to the internal plugin list * Adding support for Teams * Documentation for OpsGenie Team's * Validation for Teams * Removing Description for now * Optional fields for a User: Locale/Timezone * Removing an implemented TODO * Running makefmt * Downloading about half the internet Someone witty might simply sign this commit with "npm install" * Adding validation to the user object * Fixing the docs * Adding a test creating multple users * Prompting for the API Key if it's not specified * Added a test for multiple users / requested changes * Fixing the linting
This commit is contained in:
parent
f4c5b9fada
commit
05d00a93ce
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/opsgenie"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: opsgenie.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ func resourceArmCdnProfileCreate(d *schema.ResourceData, meta interface{}) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if read.ID == nil {
|
if read.ID == nil {
|
||||||
return fmt.Errorf("Cannot read CND Profile %s (resource group %s) ID", name, resGroup)
|
return fmt.Errorf("Cannot read CDN Profile %s (resource group %s) ID", name, resGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(*read.ID)
|
d.SetId(*read.ID)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpsGenieClient struct {
|
||||||
|
apiKey string
|
||||||
|
|
||||||
|
StopContext context.Context
|
||||||
|
|
||||||
|
teams client.OpsGenieTeamClient
|
||||||
|
users client.OpsGenieUserClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config defines the configuration options for the OpsGenie client
|
||||||
|
type Config struct {
|
||||||
|
ApiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns a new OpsGenie client
|
||||||
|
func (c *Config) Client() (*OpsGenieClient, error) {
|
||||||
|
opsGenie := new(client.OpsGenieClient)
|
||||||
|
opsGenie.SetAPIKey(c.ApiKey)
|
||||||
|
client := OpsGenieClient{}
|
||||||
|
|
||||||
|
log.Printf("[INFO] OpsGenie client configured")
|
||||||
|
|
||||||
|
teamsClient, err := opsGenie.Team()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client.teams = *teamsClient
|
||||||
|
|
||||||
|
usersClient, err := opsGenie.User()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client.users = *usersClient
|
||||||
|
|
||||||
|
return &client, nil
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dataSourceOpsGenieUser() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Read: dataSourceOpsGenieUserRead,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"username": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataSourceOpsGenieUserRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).users
|
||||||
|
|
||||||
|
username := d.Get("username").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Reading OpsGenie user '%s'", username)
|
||||||
|
|
||||||
|
o := user.ListUsersRequest{}
|
||||||
|
resp, err := client.List(o)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *user.GetUserResponse
|
||||||
|
|
||||||
|
if len(resp.Users) > 0 {
|
||||||
|
for _, user := range resp.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
found = &user
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
return fmt.Errorf("Unable to locate any user with the username: %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(found.Id)
|
||||||
|
d.Set("username", found.Username)
|
||||||
|
d.Set("full_name", found.Fullname)
|
||||||
|
d.Set("role", found.Role)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDataSourceOpsGenieUser_Basic(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccDataSourceOpsGenieUserConfig(ri),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccDataSourceOpsGenieUser("opsgenie_user.test", "data.opsgenie_user.by_username"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDataSourceOpsGenieUser(src, n string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
srcR := s.RootModule().Resources[src]
|
||||||
|
srcA := srcR.Primary.Attributes
|
||||||
|
|
||||||
|
r := s.RootModule().Resources[n]
|
||||||
|
a := r.Primary.Attributes
|
||||||
|
|
||||||
|
if a["id"] == "" {
|
||||||
|
return fmt.Errorf("Expected to get a user ID from OpsGenie")
|
||||||
|
}
|
||||||
|
|
||||||
|
testAtts := []string{"username", "full_name", "role"}
|
||||||
|
|
||||||
|
for _, att := range testAtts {
|
||||||
|
if a[att] != srcA[att] {
|
||||||
|
return fmt.Errorf("Expected the user %s to be: %s, but got: %s", att, srcA[att], a[att])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDataSourceOpsGenieUserConfig(ri int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "acctest-%d@example.tld"
|
||||||
|
full_name = "Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "opsgenie_user" "by_username" {
|
||||||
|
username = "${opsgenie_user.test.username}"
|
||||||
|
}
|
||||||
|
`, ri)
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_importBasic(t *testing.T) {
|
||||||
|
resourceName := "opsgenie_team.test"
|
||||||
|
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_basic, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_importWithUser(t *testing.T) {
|
||||||
|
resourceName := "opsgenie_team.test"
|
||||||
|
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_withUser, ri, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_importWithUserComplete(t *testing.T) {
|
||||||
|
resourceName := "opsgenie_team.test"
|
||||||
|
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_withUserComplete, ri, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccOpsGenieUser_importBasic(t *testing.T) {
|
||||||
|
resourceName := "opsgenie_user.test"
|
||||||
|
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieUser_basic, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieUserDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieUser_importComplete(t *testing.T) {
|
||||||
|
resourceName := "opsgenie_user.test"
|
||||||
|
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieUser_complete, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieUserDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider represents a resource provider in Terraform
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"api_key": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("OPSGENIE_API_KEY", nil),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
DataSourcesMap: map[string]*schema.Resource{
|
||||||
|
"opsgenie_user": dataSourceOpsGenieUser(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"opsgenie_team": resourceOpsGenieTeam(),
|
||||||
|
"opsgenie_user": resourceOpsGenieUser(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: providerConfigure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func providerConfigure(data *schema.ResourceData) (interface{}, error) {
|
||||||
|
log.Println("[INFO] Initializing OpsGenie client")
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
ApiKey: data.Get("api_key").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Client()
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"opsgenie": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
apiKey := os.Getenv("OPSGENIE_API_KEY")
|
||||||
|
|
||||||
|
if apiKey == "" {
|
||||||
|
t.Fatal("OPSGENIE_API_KEY must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/team"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceOpsGenieTeam() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceOpsGenieTeamCreate,
|
||||||
|
Read: resourceOpsGenieTeamRead,
|
||||||
|
Update: resourceOpsGenieTeamUpdate,
|
||||||
|
Delete: resourceOpsGenieTeamDelete,
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validateOpsGenieTeamName,
|
||||||
|
},
|
||||||
|
"member": {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"username": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"role": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "user",
|
||||||
|
ValidateFunc: validateOpsGenieTeamRole,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieTeamCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).teams
|
||||||
|
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
|
||||||
|
createRequest := team.CreateTeamRequest{
|
||||||
|
Name: name,
|
||||||
|
Members: expandOpsGenieTeamMembers(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Creating OpsGenie team '%s'", name)
|
||||||
|
|
||||||
|
createResponse, err := client.Create(createRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkOpsGenieResponse(createResponse.Code, createResponse.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest := team.GetTeamRequest{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse, err := client.Get(getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(getResponse.Id)
|
||||||
|
|
||||||
|
return resourceOpsGenieTeamRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieTeamRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).teams
|
||||||
|
|
||||||
|
listRequest := team.ListTeamsRequest{}
|
||||||
|
listResponse, err := client.List(listRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *team.GetTeamResponse
|
||||||
|
for _, team := range listResponse.Teams {
|
||||||
|
if team.Id == d.Id() {
|
||||||
|
found = &team
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
d.SetId("")
|
||||||
|
log.Printf("[INFO] Team %q not found. Removing from state", d.Get("name").(string))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest := team.GetTeamRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse, err := client.Get(getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", getResponse.Name)
|
||||||
|
d.Set("member", flattenOpsGenieTeamMembers(getResponse.Members))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieTeamUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).teams
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
|
||||||
|
updateRequest := team.UpdateTeamRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
Name: name,
|
||||||
|
Members: expandOpsGenieTeamMembers(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Updating OpsGenie team '%s'", name)
|
||||||
|
|
||||||
|
updateResponse, err := client.Update(updateRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkOpsGenieResponse(updateResponse.Code, updateResponse.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieTeamDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[INFO] Deleting OpsGenie team '%s'", d.Get("name").(string))
|
||||||
|
client := meta.(*OpsGenieClient).teams
|
||||||
|
|
||||||
|
deleteRequest := team.DeleteTeamRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Delete(deleteRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenOpsGenieTeamMembers(input []team.Member) []interface{} {
|
||||||
|
members := make([]interface{}, 0, len(input))
|
||||||
|
for _, inputMember := range input {
|
||||||
|
outputMember := make(map[string]interface{})
|
||||||
|
outputMember["username"] = inputMember.User
|
||||||
|
outputMember["role"] = inputMember.Role
|
||||||
|
|
||||||
|
members = append(members, outputMember)
|
||||||
|
}
|
||||||
|
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandOpsGenieTeamMembers(d *schema.ResourceData) []team.Member {
|
||||||
|
input := d.Get("member").([]interface{})
|
||||||
|
|
||||||
|
members := make([]team.Member, 0, len(input))
|
||||||
|
if input == nil {
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range input {
|
||||||
|
config := v.(map[string]interface{})
|
||||||
|
|
||||||
|
username := config["username"].(string)
|
||||||
|
role := config["role"].(string)
|
||||||
|
|
||||||
|
member := team.Member{
|
||||||
|
User: username,
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
|
||||||
|
members = append(members, member)
|
||||||
|
}
|
||||||
|
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsGenieTeamName(v interface{}, k string) (ws []string, errors []error) {
|
||||||
|
value := v.(string)
|
||||||
|
if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(value) {
|
||||||
|
errors = append(errors, fmt.Errorf(
|
||||||
|
"only alpha numeric characters and underscores are allowed in %q: %q", k, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(value) >= 100 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q cannot be longer than 100 characters: %q %d", k, value, len(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsGenieTeamRole(v interface{}, k string) (ws []string, errors []error) {
|
||||||
|
value := strings.ToLower(v.(string))
|
||||||
|
families := map[string]bool{
|
||||||
|
"admin": true,
|
||||||
|
"user": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !families[value] {
|
||||||
|
errors = append(errors, fmt.Errorf("OpsGenie Team Role can only be 'Admin' or 'User'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/team"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeamName_validation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Value string
|
||||||
|
ErrCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Value: "hello-world",
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "hello_world",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "helloWorld",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "helloworld12",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "hello@world",
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "qfvbdsbvipqdbwsbddbdcwqffewsqwcdw21ddwqwd3324120",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "qfvbdsbvipqdbwsbddbdcwqffewsqwcdw21ddwqwd33241202qfvbdsbvipqdbwsbddbdcwqffewsqwcdw21ddwqwd33241202",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "qfvbdsbvipqdbwsbddbdcwqfjjfewsqwcdw21ddwqwd3324120qfvbdsbvipqdbwsbddbdcwqfjjfewsqwcdw21ddwqwd3324120",
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, errors := validateOpsGenieTeamName(tc.Value, "opsgenie_team")
|
||||||
|
|
||||||
|
if len(errors) != tc.ErrCount {
|
||||||
|
t.Fatalf("Expected the OpsGenie Team Name to trigger a validation error: %v", errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeamRole_validation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Value string
|
||||||
|
ErrCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Value: "admin",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "user",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "custom",
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, errors := validateOpsGenieTeamRole(tc.Value, "opsgenie_team")
|
||||||
|
|
||||||
|
if len(errors) != tc.ErrCount {
|
||||||
|
t.Fatalf("Expected the OpsGenie Team Role to trigger a validation error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_basic(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_basic, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieTeamExists("opsgenie_team.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_withUser(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_withUser, ri, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieTeamExists("opsgenie_team.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_withUserComplete(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_withUserComplete, ri, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieTeamExists("opsgenie_team.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieTeam_withMultipleUsers(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieTeam_withMultipleUsers, ri, ri, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieTeamExists("opsgenie_team.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckOpsGenieTeamDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*OpsGenieClient).teams
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "opsgenie_team" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
req := team.GetTeamRequest{
|
||||||
|
Id: rs.Primary.Attributes["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := client.Get(req)
|
||||||
|
if result != nil {
|
||||||
|
return fmt.Errorf("Team still exists:\n%#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckOpsGenieTeamExists(name string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
// Ensure we have enough information in state to look up in API
|
||||||
|
rs, ok := s.RootModule().Resources[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := rs.Primary.Attributes["id"]
|
||||||
|
name := rs.Primary.Attributes["name"]
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*OpsGenieClient).teams
|
||||||
|
|
||||||
|
req := team.GetTeamRequest{
|
||||||
|
Id: rs.Primary.Attributes["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := client.Get(req)
|
||||||
|
if result == nil {
|
||||||
|
return fmt.Errorf("Bad: Team %q (name: %q) does not exist", id, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccOpsGenieTeam_basic = `
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
name = "acctest%d"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var testAccOpsGenieTeam_withUser = `
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "acctest-%d@example.tld"
|
||||||
|
full_name = "Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
name = "acctest%d"
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.test.username}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var testAccOpsGenieTeam_withUserComplete = `
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "acctest-%d@example.tld"
|
||||||
|
full_name = "Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
name = "acctest%d"
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.test.username}"
|
||||||
|
role = "user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var testAccOpsGenieTeam_withMultipleUsers = `
|
||||||
|
resource "opsgenie_user" "first" {
|
||||||
|
username = "acctest-1-%d@example.tld"
|
||||||
|
full_name = "First Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
resource "opsgenie_user" "second" {
|
||||||
|
username = "acctest-2-%d@example.tld"
|
||||||
|
full_name = "Second Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
name = "acctest%d"
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.first.username}"
|
||||||
|
}
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.second.username}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,211 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceOpsGenieUser() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceOpsGenieUserCreate,
|
||||||
|
Read: resourceOpsGenieUserRead,
|
||||||
|
Update: resourceOpsGenieUserUpdate,
|
||||||
|
Delete: resourceOpsGenieUserDelete,
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"username": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
ForceNew: true,
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validateOpsGenieUserUsername,
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validateOpsGenieUserFullName,
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validateOpsGenieUserRole,
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "en_US",
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "America/New_York",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieUserCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).users
|
||||||
|
|
||||||
|
username := d.Get("username").(string)
|
||||||
|
fullName := d.Get("full_name").(string)
|
||||||
|
role := d.Get("role").(string)
|
||||||
|
locale := d.Get("locale").(string)
|
||||||
|
timeZone := d.Get("timezone").(string)
|
||||||
|
|
||||||
|
createRequest := user.CreateUserRequest{
|
||||||
|
Username: username,
|
||||||
|
Fullname: fullName,
|
||||||
|
Role: role,
|
||||||
|
Locale: locale,
|
||||||
|
Timezone: timeZone,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Creating OpsGenie user '%s'", username)
|
||||||
|
createResponse, err := client.Create(createRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkOpsGenieResponse(createResponse.Code, createResponse.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest := user.GetUserRequest{
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse, err := client.Get(getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(getResponse.Id)
|
||||||
|
|
||||||
|
return resourceOpsGenieUserRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieUserRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).users
|
||||||
|
|
||||||
|
listRequest := user.ListUsersRequest{}
|
||||||
|
listResponse, err := client.List(listRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *user.GetUserResponse
|
||||||
|
for _, user := range listResponse.Users {
|
||||||
|
if user.Id == d.Id() {
|
||||||
|
found = &user
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
d.SetId("")
|
||||||
|
log.Printf("[INFO] User %q not found. Removing from state", d.Get("username").(string))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest := user.GetUserRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse, err := client.Get(getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("username", getResponse.Username)
|
||||||
|
d.Set("full_name", getResponse.Fullname)
|
||||||
|
d.Set("role", getResponse.Role)
|
||||||
|
d.Set("locale", getResponse.Locale)
|
||||||
|
d.Set("timezone", getResponse.Timezone)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieUserUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*OpsGenieClient).users
|
||||||
|
|
||||||
|
username := d.Get("username").(string)
|
||||||
|
fullName := d.Get("full_name").(string)
|
||||||
|
role := d.Get("role").(string)
|
||||||
|
locale := d.Get("locale").(string)
|
||||||
|
timeZone := d.Get("timezone").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Updating OpsGenie user '%s'", username)
|
||||||
|
|
||||||
|
updateRequest := user.UpdateUserRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
Fullname: fullName,
|
||||||
|
Role: role,
|
||||||
|
Locale: locale,
|
||||||
|
Timezone: timeZone,
|
||||||
|
}
|
||||||
|
|
||||||
|
updateResponse, err := client.Update(updateRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkOpsGenieResponse(updateResponse.Code, updateResponse.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceOpsGenieUserDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[INFO] Deleting OpsGenie user '%s'", d.Get("username").(string))
|
||||||
|
client := meta.(*OpsGenieClient).users
|
||||||
|
|
||||||
|
deleteRequest := user.DeleteUserRequest{
|
||||||
|
Id: d.Id(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Delete(deleteRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsGenieUserUsername(v interface{}, k string) (ws []string, errors []error) {
|
||||||
|
value := v.(string)
|
||||||
|
|
||||||
|
if len(value) >= 100 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q cannot be longer than 100 characters: %q %d", k, value, len(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsGenieUserFullName(v interface{}, k string) (ws []string, errors []error) {
|
||||||
|
value := v.(string)
|
||||||
|
|
||||||
|
if len(value) >= 512 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q cannot be longer than 512 characters: %q %d", k, value, len(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsGenieUserRole(v interface{}, k string) (ws []string, errors []error) {
|
||||||
|
value := v.(string)
|
||||||
|
|
||||||
|
if len(value) >= 512 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q cannot be longer than 512 characters: %q %d", k, value, len(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccOpsGenieUserUsername_validation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Value string
|
||||||
|
ErrCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Value: "hello",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(99),
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(100),
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, errors := validateOpsGenieUserUsername(tc.Value, "opsgenie_team")
|
||||||
|
|
||||||
|
if len(errors) != tc.ErrCount {
|
||||||
|
t.Fatalf("Expected the OpsGenie User Username Validation to trigger a validation error: %v", errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieUserFullName_validation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Value string
|
||||||
|
ErrCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Value: "hello",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(100),
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(511),
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(512),
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, errors := validateOpsGenieUserFullName(tc.Value, "opsgenie_team")
|
||||||
|
|
||||||
|
if len(errors) != tc.ErrCount {
|
||||||
|
t.Fatalf("Expected the OpsGenie User Full Name Validation to trigger a validation error: %v", errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieUserRole_validation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Value string
|
||||||
|
ErrCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Value: "hello",
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(100),
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(511),
|
||||||
|
ErrCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: acctest.RandString(512),
|
||||||
|
ErrCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, errors := validateOpsGenieUserRole(tc.Value, "opsgenie_team")
|
||||||
|
|
||||||
|
if len(errors) != tc.ErrCount {
|
||||||
|
t.Fatalf("Expected the OpsGenie User Role Validation to trigger a validation error: %v", errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieUser_basic(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieUser_basic, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieUserDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieUserExists("opsgenie_user.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccOpsGenieUser_complete(t *testing.T) {
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
config := fmt.Sprintf(testAccOpsGenieUser_complete, ri)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testCheckOpsGenieUserDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: config,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testCheckOpsGenieUserExists("opsgenie_user.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckOpsGenieUserDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*OpsGenieClient).users
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "opsgenie_user" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
req := user.GetUserRequest{
|
||||||
|
Id: rs.Primary.Attributes["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := client.Get(req)
|
||||||
|
if result != nil {
|
||||||
|
return fmt.Errorf("User still exists:\n%#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckOpsGenieUserExists(name string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
// Ensure we have enough information in state to look up in API
|
||||||
|
rs, ok := s.RootModule().Resources[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := rs.Primary.Attributes["id"]
|
||||||
|
username := rs.Primary.Attributes["username"]
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*OpsGenieClient).users
|
||||||
|
|
||||||
|
req := user.GetUserRequest{
|
||||||
|
Id: rs.Primary.Attributes["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := client.Get(req)
|
||||||
|
if result == nil {
|
||||||
|
return fmt.Errorf("Bad: User %q (username: %q) does not exist", id, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccOpsGenieUser_basic = `
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "acctest-%d@example.tld"
|
||||||
|
full_name = "Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var testAccOpsGenieUser_complete = `
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "acctest-%d@example.tld"
|
||||||
|
full_name = "Acceptance Test User"
|
||||||
|
role = "User"
|
||||||
|
locale = "en_GB"
|
||||||
|
timezone = "Etc/GMT"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,14 @@
|
||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkOpsGenieResponse(code int, status string) error {
|
||||||
|
if code == http.StatusOK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unexpected Status Code '%d', Response '%s'", code, status)
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ import (
|
||||||
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
|
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
|
||||||
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
|
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
|
||||||
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
|
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
|
||||||
|
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
|
||||||
packetprovider "github.com/hashicorp/terraform/builtin/providers/packet"
|
packetprovider "github.com/hashicorp/terraform/builtin/providers/packet"
|
||||||
pagerdutyprovider "github.com/hashicorp/terraform/builtin/providers/pagerduty"
|
pagerdutyprovider "github.com/hashicorp/terraform/builtin/providers/pagerduty"
|
||||||
postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql"
|
postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql"
|
||||||
|
@ -106,6 +107,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
||||||
"nomad": nomadprovider.Provider,
|
"nomad": nomadprovider.Provider,
|
||||||
"null": nullprovider.Provider,
|
"null": nullprovider.Provider,
|
||||||
"openstack": openstackprovider.Provider,
|
"openstack": openstackprovider.Provider,
|
||||||
|
"opsgenie": opsgenieprovider.Provider,
|
||||||
"packet": packetprovider.Provider,
|
"packet": packetprovider.Provider,
|
||||||
"pagerduty": pagerdutyprovider.Provider,
|
"pagerduty": pagerdutyprovider.Provider,
|
||||||
"postgresql": postgresqlprovider.Provider,
|
"postgresql": postgresqlprovider.Provider,
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2012, Cloud Instruments Co., Ltd. <info@cin.io>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Cloud Instruments Co., Ltd. nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,116 @@
|
||||||
|
Seelog
|
||||||
|
=======
|
||||||
|
|
||||||
|
Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log messages.
|
||||||
|
It is natively written in the [Go](http://golang.org/) programming language.
|
||||||
|
|
||||||
|
[![Build Status](https://drone.io/github.com/cihub/seelog/status.png)](https://drone.io/github.com/cihub/seelog/latest)
|
||||||
|
|
||||||
|
Features
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Xml configuring to be able to change logger parameters without recompilation
|
||||||
|
* Changing configurations on the fly without app restart
|
||||||
|
* Possibility to set different log configurations for different project files and functions
|
||||||
|
* Adjustable message formatting
|
||||||
|
* Simultaneous log output to multiple streams
|
||||||
|
* Choosing logger priority strategy to minimize performance hit
|
||||||
|
* Different output writers
|
||||||
|
* Console writer
|
||||||
|
* File writer
|
||||||
|
* Buffered writer (Chunk writer)
|
||||||
|
* Rolling log writer (Logging with rotation)
|
||||||
|
* SMTP writer
|
||||||
|
* Others... (See [Wiki](https://github.com/cihub/seelog/wiki))
|
||||||
|
* Log message wrappers (JSON, XML, etc.)
|
||||||
|
* Global variables and functions for easy usage in standalone apps
|
||||||
|
* Functions for flexible usage in libraries
|
||||||
|
|
||||||
|
Quick-start
|
||||||
|
-----------
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer log.Flush()
|
||||||
|
log.Info("Hello from Seelog!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you don't have the Go development environment installed, visit the
|
||||||
|
[Getting Started](http://golang.org/doc/install.html) document and follow the instructions. Once you're ready, execute the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/cihub/seelog
|
||||||
|
```
|
||||||
|
|
||||||
|
*IMPORTANT*: If you are not using the latest release version of Go, check out this [wiki page](https://github.com/cihub/seelog/wiki/Notes-on-'go-get')
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Seelog has github wiki pages, which contain detailed how-tos references: https://github.com/cihub/seelog/wiki
|
||||||
|
|
||||||
|
Examples
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Seelog examples can be found here: [seelog-examples](https://github.com/cihub/seelog-examples)
|
||||||
|
|
||||||
|
Issues
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Feel free to push issues that could make Seelog better: https://github.com/cihub/seelog/issues
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
---------------
|
||||||
|
* **v2.6** : Config using code and custom formatters
|
||||||
|
* Configuration using code in addition to xml (All internal receiver/dispatcher/logger types are now exported).
|
||||||
|
* Custom formatters. Check [wiki](https://github.com/cihub/seelog/wiki/Custom-formatters)
|
||||||
|
* Bugfixes and internal improvements.
|
||||||
|
* **v2.5** : Interaction with other systems. Part 2: custom receivers
|
||||||
|
* Finished custom receivers feature. Check [wiki](https://github.com/cihub/seelog/wiki/custom-receivers)
|
||||||
|
* Added 'LoggerFromCustomReceiver'
|
||||||
|
* Added 'LoggerFromWriterWithMinLevelAndFormat'
|
||||||
|
* Added 'LoggerFromCustomReceiver'
|
||||||
|
* Added 'LoggerFromParamConfigAs...'
|
||||||
|
* **v2.4** : Interaction with other systems. Part 1: wrapping seelog
|
||||||
|
* Added configurable caller stack skip logic
|
||||||
|
* Added 'SetAdditionalStackDepth' to 'LoggerInterface'
|
||||||
|
* **v2.3** : Rethinking 'rolling' receiver
|
||||||
|
* Reimplemented 'rolling' receiver
|
||||||
|
* Added 'Max rolls' feature for 'rolling' receiver with type='date'
|
||||||
|
* Fixed 'rolling' receiver issue: renaming on Windows
|
||||||
|
* **v2.2** : go1.0 compatibility point [go1.0 tag]
|
||||||
|
* Fixed internal bugs
|
||||||
|
* Added 'ANSI n [;k]' format identifier: %EscN
|
||||||
|
* Made current release go1 compatible
|
||||||
|
* **v2.1** : Some new features
|
||||||
|
* Rolling receiver archiving option.
|
||||||
|
* Added format identifier: %Line
|
||||||
|
* Smtp: added paths to PEM files directories
|
||||||
|
* Added format identifier: %FuncShort
|
||||||
|
* Warn, Error and Critical methods now return an error
|
||||||
|
* **v2.0** : Second major release. BREAKING CHANGES.
|
||||||
|
* Support of binaries with stripped symbols
|
||||||
|
* Added log strategy: adaptive
|
||||||
|
* Critical message now forces Flush()
|
||||||
|
* Added predefined formats: xml-debug, xml-debug-short, xml, xml-short, json-debug, json-debug-short, json, json-short, debug, debug-short, fast
|
||||||
|
* Added receiver: conn (network connection writer)
|
||||||
|
* BREAKING CHANGE: added Tracef, Debugf, Infof, etc. to satisfy the print/printf principle
|
||||||
|
* Bug fixes
|
||||||
|
* **v1.0** : Initial release. Features:
|
||||||
|
* Xml config
|
||||||
|
* Changing configurations on the fly without app restart
|
||||||
|
* Contraints and exceptions
|
||||||
|
* Formatting
|
||||||
|
* Log strategies: sync, async loop, async timer
|
||||||
|
* Receivers: buffered, console, file, rolling, smtp
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive/gzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is the interface for reading files from an archive.
|
||||||
|
type Reader interface {
|
||||||
|
NextFile() (name string, err error)
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCloser is the interface that groups Reader with the Close method.
|
||||||
|
type ReadCloser interface {
|
||||||
|
Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is the interface for writing files to an archived format.
|
||||||
|
type Writer interface {
|
||||||
|
NextFile(name string, fi os.FileInfo) error
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteCloser is the interface that groups Writer with the Close method.
|
||||||
|
type WriteCloser interface {
|
||||||
|
Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopCloser struct{ Reader }
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
// NopCloser returns a ReadCloser with a no-op Close method wrapping the
|
||||||
|
// provided Reader r.
|
||||||
|
func NopCloser(r Reader) ReadCloser {
|
||||||
|
return nopCloser{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies from src to dest until either EOF is reached on src or an error
|
||||||
|
// occurs.
|
||||||
|
//
|
||||||
|
// When the archive format of src matches that of dst, Copy streams the files
|
||||||
|
// directly into dst. Otherwise, copy buffers the contents to disk to compute
|
||||||
|
// headers before writing to dst.
|
||||||
|
func Copy(dst Writer, src Reader) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case tarReader:
|
||||||
|
if dst, ok := dst.(tarWriter); ok {
|
||||||
|
return copyTar(dst, src)
|
||||||
|
}
|
||||||
|
case zipReader:
|
||||||
|
if dst, ok := dst.(zipWriter); ok {
|
||||||
|
return copyZip(dst, src)
|
||||||
|
}
|
||||||
|
// Switch on concrete type because gzip has no special methods
|
||||||
|
case *gzip.Reader:
|
||||||
|
if dst, ok := dst.(*gzip.Writer); ok {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyBuffer(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBuffer(dst Writer, src Reader) (err error) {
|
||||||
|
const defaultFileMode = 0666
|
||||||
|
|
||||||
|
buf, err := ioutil.TempFile("", "archive_copy_buffer")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(buf.Name()) // Do not care about failure removing temp
|
||||||
|
defer buf.Close() // Do not care about failure closing temp
|
||||||
|
for {
|
||||||
|
// Handle the next file
|
||||||
|
name, err := src.NextFile()
|
||||||
|
switch err {
|
||||||
|
case io.EOF: // Done copying
|
||||||
|
return nil
|
||||||
|
default: // Failed to write: bail out
|
||||||
|
return err
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer the file
|
||||||
|
if _, err := io.Copy(buf, src); err != nil {
|
||||||
|
return fmt.Errorf("buffer to disk: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to the start of the file for full file copy
|
||||||
|
if _, err := buf.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set desired file permissions
|
||||||
|
if err := os.Chmod(buf.Name(), defaultFileMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fi, err := buf.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the buffered file
|
||||||
|
if err := dst.NextFile(name, fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(dst, buf); err != nil {
|
||||||
|
return fmt.Errorf("copy to dst: %v", err)
|
||||||
|
}
|
||||||
|
if err := buf.Truncate(0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := buf.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tarReader interface {
|
||||||
|
Next() (*tar.Header, error)
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tarWriter interface {
|
||||||
|
WriteHeader(hdr *tar.Header) error
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipReader interface {
|
||||||
|
Files() []*zip.File
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipWriter interface {
|
||||||
|
CreateHeader(fh *zip.FileHeader) (io.Writer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyTar(w tarWriter, r tarReader) error {
|
||||||
|
for {
|
||||||
|
hdr, err := r.Next()
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
return nil
|
||||||
|
default: // Handle error
|
||||||
|
return err
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
info := hdr.FileInfo()
|
||||||
|
// Skip directories
|
||||||
|
if info.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := w.WriteHeader(hdr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyZip(zw zipWriter, r zipReader) error {
|
||||||
|
for _, f := range r.Files() {
|
||||||
|
if err := copyZipFile(zw, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyZipFile(zw zipWriter, f *zip.File) error {
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close() // Read-only
|
||||||
|
|
||||||
|
hdr := f.FileHeader
|
||||||
|
hdr.SetModTime(time.Now())
|
||||||
|
w, err := zw.CreateHeader(&hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, rc)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package archive_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive"
|
||||||
|
"github.com/cihub/seelog/archive/gzip"
|
||||||
|
"github.com/cihub/seelog/archive/tar"
|
||||||
|
"github.com/cihub/seelog/archive/zip"
|
||||||
|
"github.com/cihub/seelog/io/iotest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gzipType = "gzip"
|
||||||
|
tarType = "tar"
|
||||||
|
zipType = "zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
var types = []string{gzipType, tarType, zipType}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
name string
|
||||||
|
contents []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oneFile = []file{
|
||||||
|
{
|
||||||
|
name: "file1",
|
||||||
|
contents: []byte("This is a single log."),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
twoFiles = []file{
|
||||||
|
{
|
||||||
|
name: "file1",
|
||||||
|
contents: []byte("This is a log."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file2",
|
||||||
|
contents: []byte("This is another log."),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
srcType, dstType string
|
||||||
|
in []file
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyTests() map[string]testCase {
|
||||||
|
// types X types X files
|
||||||
|
tests := make(map[string]testCase, len(types)*len(types)*2)
|
||||||
|
for _, srct := range types {
|
||||||
|
for _, dstt := range types {
|
||||||
|
tests[fmt.Sprintf("%s to %s: one file", srct, dstt)] = testCase{
|
||||||
|
srcType: srct,
|
||||||
|
dstType: dstt,
|
||||||
|
in: oneFile,
|
||||||
|
}
|
||||||
|
// gzip does not handle more than one file
|
||||||
|
if srct != gzipType && dstt != gzipType {
|
||||||
|
tests[fmt.Sprintf("%s to %s: two files", srct, dstt)] = testCase{
|
||||||
|
srcType: srct,
|
||||||
|
dstType: dstt,
|
||||||
|
in: twoFiles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
srcb, dstb := new(bytes.Buffer), new(bytes.Buffer)
|
||||||
|
for tname, tt := range copyTests() {
|
||||||
|
// Reset buffers between tests
|
||||||
|
srcb.Reset()
|
||||||
|
dstb.Reset()
|
||||||
|
|
||||||
|
// Last file name (needed for gzip.NewReader)
|
||||||
|
var fname string
|
||||||
|
|
||||||
|
// Seed the src
|
||||||
|
srcw := writer(t, tname, srcb, tt.srcType)
|
||||||
|
for _, f := range tt.in {
|
||||||
|
srcw.NextFile(f.name, iotest.FileInfo(t, f.contents))
|
||||||
|
mustCopy(t, tname, srcw, bytes.NewReader(f.contents))
|
||||||
|
fname = f.name
|
||||||
|
}
|
||||||
|
mustClose(t, tname, srcw)
|
||||||
|
|
||||||
|
// Perform the copy
|
||||||
|
srcr := reader(t, tname, srcb, tt.srcType, fname)
|
||||||
|
dstw := writer(t, tname, dstb, tt.dstType)
|
||||||
|
if err := archive.Copy(dstw, srcr); err != nil {
|
||||||
|
t.Fatalf("%s: %v", tname, err)
|
||||||
|
}
|
||||||
|
srcr.Close() // Read-only
|
||||||
|
mustClose(t, tname, dstw)
|
||||||
|
|
||||||
|
// Read back dst to confirm our expectations
|
||||||
|
dstr := reader(t, tname, dstb, tt.dstType, fname)
|
||||||
|
for _, want := range tt.in {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
name, err := dstr.NextFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %v", tname, err)
|
||||||
|
}
|
||||||
|
mustCopy(t, tname, buf, dstr)
|
||||||
|
got := file{
|
||||||
|
name: name,
|
||||||
|
contents: buf.Bytes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case got.name != want.name:
|
||||||
|
t.Errorf("%s: got file %q but want file %q",
|
||||||
|
tname, got.name, want.name)
|
||||||
|
|
||||||
|
case !bytes.Equal(got.contents, want.contents):
|
||||||
|
t.Errorf("%s: mismatched contents in %q: got %q but want %q",
|
||||||
|
tname, got.name, got.contents, want.contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dstr.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writer(t *testing.T, tname string, w io.Writer, atype string) archive.WriteCloser {
|
||||||
|
switch atype {
|
||||||
|
case gzipType:
|
||||||
|
return gzip.NewWriter(w)
|
||||||
|
case tarType:
|
||||||
|
return tar.NewWriter(w)
|
||||||
|
case zipType:
|
||||||
|
return zip.NewWriter(w)
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: unrecognized archive type: %s", tname, atype)
|
||||||
|
panic("execution continued after (*testing.T).Fatalf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func reader(t *testing.T, tname string, buf *bytes.Buffer, atype string, fname string) archive.ReadCloser {
|
||||||
|
switch atype {
|
||||||
|
case gzipType:
|
||||||
|
gr, err := gzip.NewReader(buf, fname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %v", tname, err)
|
||||||
|
}
|
||||||
|
return gr
|
||||||
|
case tarType:
|
||||||
|
return archive.NopCloser(tar.NewReader(buf))
|
||||||
|
case zipType:
|
||||||
|
zr, err := zip.NewReader(
|
||||||
|
bytes.NewReader(buf.Bytes()),
|
||||||
|
int64(buf.Len()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: new zip reader: %v", tname, err)
|
||||||
|
}
|
||||||
|
return archive.NopCloser(zr)
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: unrecognized archive type: %s", tname, atype)
|
||||||
|
panic("execution continued after (*testing.T).Fatalf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCopy(t *testing.T, tname string, dst io.Writer, src io.Reader) {
|
||||||
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
|
t.Fatalf("%s: copy: %v", tname, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustClose(t *testing.T, tname string, c io.Closer) {
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
t.Fatalf("%s: close: %v", tname, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Package gzip implements reading and writing of gzip format compressed files.
|
||||||
|
// See the compress/gzip package for more details.
|
||||||
|
package gzip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is an io.Reader that can be read to retrieve uncompressed data from a
|
||||||
|
// gzip-format compressed file.
|
||||||
|
type Reader struct {
|
||||||
|
gzip.Reader
|
||||||
|
name string
|
||||||
|
isEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates a new Reader reading the given reader.
|
||||||
|
func NewReader(r io.Reader, name string) (*Reader, error) {
|
||||||
|
gr, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Reader{
|
||||||
|
Reader: *gr,
|
||||||
|
name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile returns the file name. Calls subsequent to the first call will
|
||||||
|
// return EOF.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
if r.isEOF {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
r.isEOF = true
|
||||||
|
return r.name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is an io.WriteCloser. Writes to a Writer are compressed and written to w.
|
||||||
|
type Writer struct {
|
||||||
|
gzip.Writer
|
||||||
|
name string
|
||||||
|
noMoreFiles bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile never returns a next file, and should not be called more than once.
|
||||||
|
func (w *Writer) NextFile(name string, _ os.FileInfo) error {
|
||||||
|
if w.noMoreFiles {
|
||||||
|
return fmt.Errorf("gzip: only accepts one file: already received %q and now %q", w.name, name)
|
||||||
|
}
|
||||||
|
w.noMoreFiles = true
|
||||||
|
w.name = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer. Writes to the returned writer are compressed
|
||||||
|
// and written to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *gzip.NewWriter(w)}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader provides sequential access to the contents of a tar archive.
|
||||||
|
type Reader struct {
|
||||||
|
tar.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates a new Reader reading from r.
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{Reader: *tar.NewReader(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile advances to the next file in the tar archive.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
hdr, err := r.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hdr.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||||
|
type Writer struct {
|
||||||
|
tar.Writer
|
||||||
|
closers []io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter creates a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *tar.NewWriter(w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriteMultiCloser creates a new Writer writing to w that also closes all
|
||||||
|
// closers in order on close.
|
||||||
|
func NewWriteMultiCloser(w io.WriteCloser, closers ...io.Closer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
Writer: *tar.NewWriter(w),
|
||||||
|
closers: closers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile computes and writes a header and prepares to accept the file's
|
||||||
|
// contents.
|
||||||
|
func (w *Writer) NextFile(name string, fi os.FileInfo) error {
|
||||||
|
if name == "" {
|
||||||
|
name = fi.Name()
|
||||||
|
}
|
||||||
|
hdr, err := tar.FileInfoHeader(fi, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Name = name
|
||||||
|
return w.WriteHeader(hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the tar archive and all other closers, flushing any unwritten
|
||||||
|
// data to the underlying writer.
|
||||||
|
func (w *Writer) Close() error {
|
||||||
|
err := w.Writer.Close()
|
||||||
|
for _, c := range w.closers {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package tar_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive/tar"
|
||||||
|
"github.com/cihub/seelog/io/iotest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
name string
|
||||||
|
contents []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarTests = map[string]struct{ want []file }{
|
||||||
|
"one file": {
|
||||||
|
want: []file{
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
contents: []byte("I am a log file"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple files": {
|
||||||
|
want: []file{
|
||||||
|
{
|
||||||
|
name: "file1",
|
||||||
|
contents: []byte("I am log file 1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file2",
|
||||||
|
contents: []byte("I am log file 2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriterAndReader(t *testing.T) {
|
||||||
|
for tname, tt := range tarTests {
|
||||||
|
f, cleanup := iotest.TempFile(t)
|
||||||
|
defer cleanup()
|
||||||
|
writeFiles(t, f, tname, tt.want)
|
||||||
|
readFiles(t, f, tname, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFiles iterates through the files we want and writes them as a tarred
|
||||||
|
// file.
|
||||||
|
func writeFiles(t *testing.T, f *os.File, tname string, want []file) {
|
||||||
|
w := tar.NewWriter(f)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
// Write zipped files
|
||||||
|
for _, fwant := range want {
|
||||||
|
fi := iotest.FileInfo(t, fwant.contents)
|
||||||
|
|
||||||
|
// Write the file
|
||||||
|
err := w.NextFile(fwant.name, fi)
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
t.Fatalf("%s: write header for next file: %v", tname, err)
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, bytes.NewReader(fwant.contents)); err != nil {
|
||||||
|
t.Fatalf("%s: copy to writer: %v", tname, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFiles iterates through tarred files and ensures they are the same.
|
||||||
|
func readFiles(t *testing.T, f *os.File, tname string, want []file) {
|
||||||
|
r := tar.NewReader(f)
|
||||||
|
|
||||||
|
for _, fwant := range want {
|
||||||
|
fname, err := r.NextFile()
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Fatalf("%s: read header for next file: %v", tname, err)
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
if fname != fwant.name {
|
||||||
|
t.Fatalf("%s: incorrect file name: got %q but want %q", tname, fname, fwant.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gotContents, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: read file: %v", tname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(gotContents, fwant.contents) {
|
||||||
|
t.Errorf("%s: %q = %q but want %q", tname, fname, gotContents, fwant.contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package zip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader provides sequential access to the contents of a zip archive.
|
||||||
|
type Reader struct {
|
||||||
|
zip.Reader
|
||||||
|
unread []*zip.File
|
||||||
|
rc io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new Reader reading from r, which is assumed to have the
|
||||||
|
// given size in bytes.
|
||||||
|
func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
|
||||||
|
zr, err := zip.NewReader(r, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Reader{Reader: *zr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile advances to the next file in the zip archive.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
// Initialize unread
|
||||||
|
if r.unread == nil {
|
||||||
|
r.unread = r.Files()[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close previous file
|
||||||
|
if r.rc != nil {
|
||||||
|
r.rc.Close() // Read-only
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.unread) == 0 {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and return next unread
|
||||||
|
f := r.unread[0]
|
||||||
|
name, r.unread = f.Name, r.unread[1:]
|
||||||
|
r.rc, err = f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
return r.rc.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files returns the full list of files in the zip archive.
|
||||||
|
func (r *Reader) Files() []*zip.File {
|
||||||
|
return r.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer provides sequential writing of a zip archive.1 format.
|
||||||
|
type Writer struct {
|
||||||
|
zip.Writer
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *zip.NewWriter(w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile computes and writes a header and prepares to accept the file's
|
||||||
|
// contents.
|
||||||
|
func (w *Writer) NextFile(name string, fi os.FileInfo) error {
|
||||||
|
if name == "" {
|
||||||
|
name = fi.Name()
|
||||||
|
}
|
||||||
|
hdr, err := zip.FileInfoHeader(fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Name = name
|
||||||
|
w.w, err = w.CreateHeader(hdr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
return w.w.Write(p)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package zip_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive/zip"
|
||||||
|
"github.com/cihub/seelog/io/iotest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var zipTests = map[string]struct{ want map[string][]byte }{
|
||||||
|
"one file": {
|
||||||
|
want: map[string][]byte{
|
||||||
|
"file": []byte("I am a log file"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple files": {
|
||||||
|
want: map[string][]byte{
|
||||||
|
"file1": []byte("I am log file 1"),
|
||||||
|
"file2": []byte("I am log file 2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriterAndReader(t *testing.T) {
|
||||||
|
for tname, tt := range zipTests {
|
||||||
|
f, cleanup := iotest.TempFile(t)
|
||||||
|
defer cleanup()
|
||||||
|
writeFiles(t, f, tname, tt.want)
|
||||||
|
readFiles(t, f, tname, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFiles iterates through the files we want and writes them as a zipped
|
||||||
|
// file.
|
||||||
|
func writeFiles(t *testing.T, f *os.File, tname string, want map[string][]byte) {
|
||||||
|
w := zip.NewWriter(f)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
// Write zipped files
|
||||||
|
for fname, fbytes := range want {
|
||||||
|
fi := iotest.FileInfo(t, fbytes)
|
||||||
|
|
||||||
|
// Write the file
|
||||||
|
err := w.NextFile(fname, fi)
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
t.Fatalf("%s: write header for next file: %v", tname, err)
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, bytes.NewReader(fbytes)); err != nil {
|
||||||
|
t.Fatalf("%s: copy to writer: %v", tname, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFiles iterates through zipped files and ensures they are the same.
|
||||||
|
func readFiles(t *testing.T, f *os.File, tname string, want map[string][]byte) {
|
||||||
|
// Get zip Reader
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: stat zipped file: %v", tname, err)
|
||||||
|
}
|
||||||
|
r, err := zip.NewReader(f, fi.Size())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %v", tname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
fname, err := r.NextFile()
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Fatalf("%s: read header for next file: %v", tname, err)
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
wantBytes, ok := want[fname]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: read unwanted file: %v", tname, fname)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gotBytes, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: read file: %v", tname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(gotBytes, wantBytes) {
|
||||||
|
t.Errorf("%s: %q = %q but want %q", tname, fname, gotBytes, wantBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adaptiveLoggerMaxInterval = time.Minute
|
||||||
|
adaptiveLoggerMaxCriticalMsgCount = uint32(1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncAdaptiveLogger represents asynchronous adaptive logger which acts like
|
||||||
|
// an async timer logger, but its interval depends on the current message count
|
||||||
|
// in the queue.
|
||||||
|
//
|
||||||
|
// Interval = I, minInterval = m, maxInterval = M, criticalMsgCount = C, msgCount = c:
|
||||||
|
// I = m + (C - Min(c, C)) / C * (M - m)
|
||||||
|
type asyncAdaptiveLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
minInterval time.Duration
|
||||||
|
criticalMsgCount uint32
|
||||||
|
maxInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous adaptive logger
|
||||||
|
func NewAsyncAdaptiveLogger(
|
||||||
|
config *logConfig,
|
||||||
|
minInterval time.Duration,
|
||||||
|
maxInterval time.Duration,
|
||||||
|
criticalMsgCount uint32) (*asyncAdaptiveLogger, error) {
|
||||||
|
|
||||||
|
if minInterval <= 0 {
|
||||||
|
return nil, errors.New("async adaptive logger min interval should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxInterval > adaptiveLoggerMaxInterval {
|
||||||
|
return nil, fmt.Errorf("async adaptive logger max interval should be <= %s",
|
||||||
|
adaptiveLoggerMaxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if criticalMsgCount <= 0 {
|
||||||
|
return nil, errors.New("async adaptive logger critical msg count should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if criticalMsgCount > adaptiveLoggerMaxCriticalMsgCount {
|
||||||
|
return nil, fmt.Errorf("async adaptive logger critical msg count should be <= %s",
|
||||||
|
adaptiveLoggerMaxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
asnAdaptiveLogger := new(asyncAdaptiveLogger)
|
||||||
|
|
||||||
|
asnAdaptiveLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
asnAdaptiveLogger.minInterval = minInterval
|
||||||
|
asnAdaptiveLogger.maxInterval = maxInterval
|
||||||
|
asnAdaptiveLogger.criticalMsgCount = criticalMsgCount
|
||||||
|
|
||||||
|
go asnAdaptiveLogger.processQueue()
|
||||||
|
|
||||||
|
return asnAdaptiveLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) processItem() (closed bool, itemCount int) {
|
||||||
|
asnAdaptiveLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnAdaptiveLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnAdaptiveLogger.msgQueue.Len() == 0 && !asnAdaptiveLogger.Closed() {
|
||||||
|
asnAdaptiveLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnAdaptiveLogger.Closed() {
|
||||||
|
return true, asnAdaptiveLogger.msgQueue.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
asnAdaptiveLogger.processQueueElement()
|
||||||
|
return false, asnAdaptiveLogger.msgQueue.Len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// I = m + (C - Min(c, C)) / C * (M - m) =>
|
||||||
|
// I = m + cDiff * mDiff,
|
||||||
|
// cDiff = (C - Min(c, C)) / C)
|
||||||
|
// mDiff = (M - m)
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) calcAdaptiveInterval(msgCount int) time.Duration {
|
||||||
|
critCountF := float64(asnAdaptiveLogger.criticalMsgCount)
|
||||||
|
cDiff := (critCountF - math.Min(float64(msgCount), critCountF)) / critCountF
|
||||||
|
mDiff := float64(asnAdaptiveLogger.maxInterval - asnAdaptiveLogger.minInterval)
|
||||||
|
|
||||||
|
return asnAdaptiveLogger.minInterval + time.Duration(cDiff*mDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) processQueue() {
|
||||||
|
for !asnAdaptiveLogger.Closed() {
|
||||||
|
closed, itemCount := asnAdaptiveLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
interval := asnAdaptiveLogger.calcAdaptiveInterval(itemCount)
|
||||||
|
|
||||||
|
<-time.After(interval)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxQueueSize is the critical number of messages in the queue that result in an immediate flush.
|
||||||
|
const (
|
||||||
|
MaxQueueSize = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgQueueItem struct {
|
||||||
|
level LogLevel
|
||||||
|
context LogContextInterface
|
||||||
|
message fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncLogger represents common data for all asynchronous loggers
|
||||||
|
type asyncLogger struct {
|
||||||
|
commonLogger
|
||||||
|
msgQueue *list.List
|
||||||
|
queueHasElements *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAsyncLogger creates a new asynchronous logger
|
||||||
|
func newAsyncLogger(config *logConfig) *asyncLogger {
|
||||||
|
asnLogger := new(asyncLogger)
|
||||||
|
|
||||||
|
asnLogger.msgQueue = list.New()
|
||||||
|
asnLogger.queueHasElements = sync.NewCond(new(sync.Mutex))
|
||||||
|
|
||||||
|
asnLogger.commonLogger = *newCommonLogger(config, asnLogger)
|
||||||
|
|
||||||
|
return asnLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) innerLog(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
asnLogger.addMsgToQueue(level, context, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) Close() {
|
||||||
|
asnLogger.m.Lock()
|
||||||
|
defer asnLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.flushQueue(true)
|
||||||
|
asnLogger.config.RootDispatcher.Flush()
|
||||||
|
|
||||||
|
if err := asnLogger.config.RootDispatcher.Close(); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
asnLogger.closedM.Lock()
|
||||||
|
asnLogger.closed = true
|
||||||
|
asnLogger.closedM.Unlock()
|
||||||
|
asnLogger.queueHasElements.Broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) Flush() {
|
||||||
|
asnLogger.m.Lock()
|
||||||
|
defer asnLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.flushQueue(true)
|
||||||
|
asnLogger.config.RootDispatcher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) flushQueue(lockNeeded bool) {
|
||||||
|
if lockNeeded {
|
||||||
|
asnLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLogger.queueHasElements.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for asnLogger.msgQueue.Len() > 0 {
|
||||||
|
asnLogger.processQueueElement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) processQueueElement() {
|
||||||
|
if asnLogger.msgQueue.Len() > 0 {
|
||||||
|
backElement := asnLogger.msgQueue.Front()
|
||||||
|
msg, _ := backElement.Value.(msgQueueItem)
|
||||||
|
asnLogger.processLogMsg(msg.level, msg.message, msg.context)
|
||||||
|
asnLogger.msgQueue.Remove(backElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) addMsgToQueue(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
if asnLogger.msgQueue.Len() >= MaxQueueSize {
|
||||||
|
fmt.Printf("Seelog queue overflow: more than %v messages in the queue. Flushing.\n", MaxQueueSize)
|
||||||
|
asnLogger.flushQueue(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueItem := msgQueueItem{level, context, message}
|
||||||
|
|
||||||
|
asnLogger.msgQueue.PushBack(queueItem)
|
||||||
|
asnLogger.queueHasElements.Broadcast()
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("queue closed! Cannot process element: %d %#v", level, message)
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// asyncLoopLogger represents asynchronous logger which processes the log queue in
|
||||||
|
// a 'for' loop
|
||||||
|
type asyncLoopLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||||
|
func NewAsyncLoopLogger(config *logConfig) *asyncLoopLogger {
|
||||||
|
|
||||||
|
asnLoopLogger := new(asyncLoopLogger)
|
||||||
|
|
||||||
|
asnLoopLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
|
||||||
|
go asnLoopLogger.processQueue()
|
||||||
|
|
||||||
|
return asnLoopLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLoopLogger *asyncLoopLogger) processItem() (closed bool) {
|
||||||
|
asnLoopLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLoopLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnLoopLogger.msgQueue.Len() == 0 && !asnLoopLogger.Closed() {
|
||||||
|
asnLoopLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnLoopLogger.Closed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
asnLoopLogger.processQueueElement()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLoopLogger *asyncLoopLogger) processQueue() {
|
||||||
|
for !asnLoopLogger.Closed() {
|
||||||
|
closed := asnLoopLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncTimerLogger represents asynchronous logger which processes the log queue each
|
||||||
|
// 'duration' nanoseconds
|
||||||
|
type asyncTimerLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||||
|
func NewAsyncTimerLogger(config *logConfig, interval time.Duration) (*asyncTimerLogger, error) {
|
||||||
|
|
||||||
|
if interval <= 0 {
|
||||||
|
return nil, errors.New("async logger interval should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
asnTimerLogger := new(asyncTimerLogger)
|
||||||
|
|
||||||
|
asnTimerLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
asnTimerLogger.interval = interval
|
||||||
|
|
||||||
|
go asnTimerLogger.processQueue()
|
||||||
|
|
||||||
|
return asnTimerLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnTimerLogger *asyncTimerLogger) processItem() (closed bool) {
|
||||||
|
asnTimerLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnTimerLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnTimerLogger.msgQueue.Len() == 0 && !asnTimerLogger.Closed() {
|
||||||
|
asnTimerLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnTimerLogger.Closed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
asnTimerLogger.processQueueElement()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnTimerLogger *asyncTimerLogger) processQueue() {
|
||||||
|
for !asnTimerLogger.Closed() {
|
||||||
|
closed := asnTimerLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(asnTimerLogger.interval)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncLogger performs logging in the same goroutine where 'Trace/Debug/...'
|
||||||
|
// func was called
|
||||||
|
type syncLogger struct {
|
||||||
|
commonLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSyncLogger creates a new synchronous logger
|
||||||
|
func NewSyncLogger(config *logConfig) *syncLogger {
|
||||||
|
syncLogger := new(syncLogger)
|
||||||
|
|
||||||
|
syncLogger.commonLogger = *newCommonLogger(config, syncLogger)
|
||||||
|
|
||||||
|
return syncLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) innerLog(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
syncLogger.processLogMsg(level, message, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) Close() {
|
||||||
|
syncLogger.m.Lock()
|
||||||
|
defer syncLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !syncLogger.Closed() {
|
||||||
|
if err := syncLogger.config.RootDispatcher.Close(); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
syncLogger.closedM.Lock()
|
||||||
|
syncLogger.closed = true
|
||||||
|
syncLogger.closedM.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) Flush() {
|
||||||
|
syncLogger.m.Lock()
|
||||||
|
defer syncLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !syncLogger.Closed() {
|
||||||
|
syncLogger.config.RootDispatcher.Flush()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerFromConfigAsFile creates logger with config from file. File should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsFile(fileName string) (LoggerInterface, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
conf, err := configFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromConfigAsBytes creates a logger with config from bytes stream. Bytes should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsBytes(data []byte) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromConfigAsString creates a logger with config from a string. String should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsString(data string) (LoggerInterface, error) {
|
||||||
|
return LoggerFromConfigAsBytes([]byte(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsFile does the same as LoggerFromConfigAsFile, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsFile(fileName string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
conf, err := configFromReaderWithConfig(file, parserParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsBytes does the same as LoggerFromConfigAsBytes, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsBytes(data []byte, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromReaderWithConfig(bytes.NewBuffer(data), parserParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsString does the same as LoggerFromConfigAsString, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsString(data string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
return LoggerFromParamConfigAsBytes([]byte(data), parserParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromWriterWithMinLevel is shortcut for LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||||
|
func LoggerFromWriterWithMinLevel(output io.Writer, minLevel LogLevel) (LoggerInterface, error) {
|
||||||
|
return LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromWriterWithMinLevelAndFormat creates a proxy logger that uses io.Writer as the
|
||||||
|
// receiver with minimal level = minLevel and with specified format.
|
||||||
|
//
|
||||||
|
// All messages with level more or equal to minLevel will be written to output and
|
||||||
|
// formatted using the default seelog format.
|
||||||
|
//
|
||||||
|
// Can be called for usage with non-Seelog systems
|
||||||
|
func LoggerFromWriterWithMinLevelAndFormat(output io.Writer, minLevel LogLevel, format string) (LoggerInterface, error) {
|
||||||
|
constraints, err := NewMinMaxConstraints(minLevel, CriticalLvl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
formatter, err := NewFormatter(format)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dispatcher, err := NewSplitDispatcher(formatter, []interface{}{output})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromXMLDecoder creates logger with config from a XML decoder starting from a specific node.
|
||||||
|
// It should contain valid seelog xml, except for root node name.
|
||||||
|
func LoggerFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromXMLDecoder(xmlParser, rootNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromCustomReceiver creates a proxy logger that uses a CustomReceiver as the
|
||||||
|
// receiver.
|
||||||
|
//
|
||||||
|
// All messages will be sent to the specified custom receiver without additional
|
||||||
|
// formatting ('%Msg' format is used).
|
||||||
|
//
|
||||||
|
// Check CustomReceiver, RegisterReceiver for additional info.
|
||||||
|
//
|
||||||
|
// NOTE 1: CustomReceiver.AfterParse is only called when a receiver is instantiated
|
||||||
|
// by the config parser while parsing config. So, if you are not planning to use the
|
||||||
|
// same CustomReceiver for both proxying (via LoggerFromCustomReceiver call) and
|
||||||
|
// loading from config, just leave AfterParse implementation empty.
|
||||||
|
//
|
||||||
|
// NOTE 2: Unlike RegisterReceiver, LoggerFromCustomReceiver takes an already initialized
|
||||||
|
// instance that implements CustomReceiver. So, fill it with data and perform any initialization
|
||||||
|
// logic before calling this func and it won't be lost.
|
||||||
|
//
|
||||||
|
// So:
|
||||||
|
// * RegisterReceiver takes value just to get the reflect.Type from it and then
|
||||||
|
// instantiate it as many times as config is reloaded.
|
||||||
|
//
|
||||||
|
// * LoggerFromCustomReceiver takes value and uses it without modification and
|
||||||
|
// reinstantiation, directy passing it to the dispatcher tree.
|
||||||
|
func LoggerFromCustomReceiver(receiver CustomReceiver) (LoggerInterface, error) {
|
||||||
|
constraints, err := NewMinMaxConstraints(TraceLvl, CriticalLvl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := NewCustomReceiverDispatcherByValue(msgonlyformatter, receiver, "user-proxy", CustomReceiverInitArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dispatcher, err := NewSplitDispatcher(msgonlyformatter, []interface{}{output})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloneLogger(logger LoggerInterface) (LoggerInterface, error) {
|
||||||
|
switch logger := logger.(type) {
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected type %T", logger)
|
||||||
|
case *asyncAdaptiveLogger:
|
||||||
|
clone, err := NewAsyncAdaptiveLogger(logger.commonLogger.config, logger.minInterval, logger.maxInterval, logger.criticalMsgCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clone, nil
|
||||||
|
case *asyncLoopLogger:
|
||||||
|
return NewAsyncLoopLogger(logger.commonLogger.config), nil
|
||||||
|
case *asyncTimerLogger:
|
||||||
|
clone, err := NewAsyncTimerLogger(logger.commonLogger.config, logger.interval)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clone, nil
|
||||||
|
case *syncLogger:
|
||||||
|
return NewSyncLogger(logger.commonLogger.config), nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNodeMustHaveChildren = errors.New("node must have children")
|
||||||
|
errNodeCannotHaveChildren = errors.New("node cannot have children")
|
||||||
|
)
|
||||||
|
|
||||||
|
type unexpectedChildElementError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnexpectedChildElementError(msg string) *unexpectedChildElementError {
|
||||||
|
custmsg := "Unexpected child element: " + msg
|
||||||
|
return &unexpectedChildElementError{baseError{message: custmsg}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type missingArgumentError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMissingArgumentError(nodeName, attrName string) *missingArgumentError {
|
||||||
|
custmsg := "Output '" + nodeName + "' has no '" + attrName + "' attribute"
|
||||||
|
return &missingArgumentError{baseError{message: custmsg}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedAttributeError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnexpectedAttributeError(nodeName, attr string) *unexpectedAttributeError {
|
||||||
|
custmsg := nodeName + " has unexpected attribute: " + attr
|
||||||
|
return &unexpectedAttributeError{baseError{message: custmsg}}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggerTypeFromString uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncloggerTypeFromString = iota
|
||||||
|
asyncLooploggerTypeFromString
|
||||||
|
asyncTimerloggerTypeFromString
|
||||||
|
adaptiveLoggerTypeFromString
|
||||||
|
defaultloggerTypeFromString = asyncLooploggerTypeFromString
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncloggerTypeFromStringStr = "sync"
|
||||||
|
asyncloggerTypeFromStringStr = "asyncloop"
|
||||||
|
asyncTimerloggerTypeFromStringStr = "asynctimer"
|
||||||
|
adaptiveLoggerTypeFromStringStr = "adaptive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncTimerLoggerData represents specific data for async timer logger
|
||||||
|
type asyncTimerLoggerData struct {
|
||||||
|
AsyncInterval uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// adaptiveLoggerData represents specific data for adaptive timer logger
|
||||||
|
type adaptiveLoggerData struct {
|
||||||
|
MinInterval uint32
|
||||||
|
MaxInterval uint32
|
||||||
|
CriticalMsgCount uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var loggerTypeToStringRepresentations = map[loggerTypeFromString]string{
|
||||||
|
syncloggerTypeFromString: syncloggerTypeFromStringStr,
|
||||||
|
asyncLooploggerTypeFromString: asyncloggerTypeFromStringStr,
|
||||||
|
asyncTimerloggerTypeFromString: asyncTimerloggerTypeFromStringStr,
|
||||||
|
adaptiveLoggerTypeFromString: adaptiveLoggerTypeFromStringStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLoggerTypeFromString parses a string and returns a corresponding logger type, if successful.
|
||||||
|
func getLoggerTypeFromString(logTypeString string) (level loggerTypeFromString, found bool) {
|
||||||
|
for logType, logTypeStr := range loggerTypeToStringRepresentations {
|
||||||
|
if logTypeStr == logTypeString {
|
||||||
|
return logType, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// logConfig stores logging configuration. Contains messages dispatcher, allowed log level rules
|
||||||
|
// (general constraints and exceptions)
|
||||||
|
type logConfig struct {
|
||||||
|
Constraints logLevelConstraints // General log level rules (>min and <max, or set of allowed levels)
|
||||||
|
Exceptions []*LogLevelException // Exceptions to general rules for specific files or funcs
|
||||||
|
RootDispatcher dispatcherInterface // Root of output tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoggerConfig(c logLevelConstraints, e []*LogLevelException, d dispatcherInterface) *logConfig {
|
||||||
|
return &logConfig{c, e, d}
|
||||||
|
}
|
||||||
|
|
||||||
|
// configForParsing is used when parsing config from file: logger type is deduced from string, params
|
||||||
|
// need to be converted from attributes to values and passed to specific logger constructor. Also,
|
||||||
|
// custom registered receivers and other parse params are used in this case.
|
||||||
|
type configForParsing struct {
|
||||||
|
logConfig
|
||||||
|
LogType loggerTypeFromString
|
||||||
|
LoggerData interface{}
|
||||||
|
Params *CfgParseParams // Check cfg_parser: CfgParseParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFullLoggerConfig(
|
||||||
|
constraints logLevelConstraints,
|
||||||
|
exceptions []*LogLevelException,
|
||||||
|
rootDispatcher dispatcherInterface,
|
||||||
|
logType loggerTypeFromString,
|
||||||
|
logData interface{},
|
||||||
|
cfgParams *CfgParseParams) (*configForParsing, error) {
|
||||||
|
if constraints == nil {
|
||||||
|
return nil, errors.New("constraints can not be nil")
|
||||||
|
}
|
||||||
|
if rootDispatcher == nil {
|
||||||
|
return nil, errors.New("rootDispatcher can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := new(configForParsing)
|
||||||
|
config.Constraints = constraints
|
||||||
|
config.Exceptions = exceptions
|
||||||
|
config.RootDispatcher = rootDispatcher
|
||||||
|
config.LogType = logType
|
||||||
|
config.LoggerData = logData
|
||||||
|
config.Params = cfgParams
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true if logging with specified log level is allowed in current context.
|
||||||
|
// If any of exception patterns match current context, then exception constraints are applied. Otherwise,
|
||||||
|
// the general constraints are used.
|
||||||
|
func (config *logConfig) IsAllowed(level LogLevel, context LogContextInterface) bool {
|
||||||
|
allowed := config.Constraints.IsAllowed(level) // General rule
|
||||||
|
|
||||||
|
// Exceptions:
|
||||||
|
if context.IsValid() {
|
||||||
|
for _, exception := range config.Exceptions {
|
||||||
|
if exception.MatchesContext(context) {
|
||||||
|
return exception.IsAllowed(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents constraints which form a general rule for log levels selection
|
||||||
|
type logLevelConstraints interface {
|
||||||
|
IsAllowed(level LogLevel) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minMaxConstraints represents constraints which use minimal and maximal allowed log levels.
|
||||||
|
type minMaxConstraints struct {
|
||||||
|
min LogLevel
|
||||||
|
max LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMinMaxConstraints creates a new minMaxConstraints struct with the specified min and max levels.
|
||||||
|
func NewMinMaxConstraints(min LogLevel, max LogLevel) (*minMaxConstraints, error) {
|
||||||
|
if min > max {
|
||||||
|
return nil, fmt.Errorf("min level can't be greater than max. Got min: %d, max: %d", min, max)
|
||||||
|
}
|
||||||
|
if min < TraceLvl || min > CriticalLvl {
|
||||||
|
return nil, fmt.Errorf("min level can't be less than Trace or greater than Critical. Got min: %d", min)
|
||||||
|
}
|
||||||
|
if max < TraceLvl || max > CriticalLvl {
|
||||||
|
return nil, fmt.Errorf("max level can't be less than Trace or greater than Critical. Got max: %d", max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &minMaxConstraints{min, max}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true, if log level is in [min, max] range (inclusive).
|
||||||
|
func (minMaxConstr *minMaxConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
return level >= minMaxConstr.min && level <= minMaxConstr.max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (minMaxConstr *minMaxConstraints) String() string {
|
||||||
|
return fmt.Sprintf("Min: %s. Max: %s", minMaxConstr.min, minMaxConstr.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================
|
||||||
|
|
||||||
|
// A listConstraints represents constraints which use allowed log levels list.
|
||||||
|
type listConstraints struct {
|
||||||
|
allowedLevels map[LogLevel]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListConstraints creates a new listConstraints struct with the specified allowed levels.
|
||||||
|
func NewListConstraints(allowList []LogLevel) (*listConstraints, error) {
|
||||||
|
if allowList == nil {
|
||||||
|
return nil, errors.New("list can't be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
allowLevels, err := createMapFromList(allowList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = validateOffLevel(allowLevels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listConstraints{allowLevels}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (listConstr *listConstraints) String() string {
|
||||||
|
allowedList := "List: "
|
||||||
|
|
||||||
|
listLevel := make([]string, len(listConstr.allowedLevels))
|
||||||
|
|
||||||
|
var logLevel LogLevel
|
||||||
|
i := 0
|
||||||
|
for logLevel = TraceLvl; logLevel <= Off; logLevel++ {
|
||||||
|
if listConstr.allowedLevels[logLevel] {
|
||||||
|
listLevel[i] = logLevel.String()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedList += strings.Join(listLevel, ",")
|
||||||
|
|
||||||
|
return allowedList
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMapFromList(allowedList []LogLevel) (map[LogLevel]bool, error) {
|
||||||
|
allowedLevels := make(map[LogLevel]bool, 0)
|
||||||
|
for _, level := range allowedList {
|
||||||
|
if level < TraceLvl || level > Off {
|
||||||
|
return nil, fmt.Errorf("level can't be less than Trace or greater than Critical. Got level: %d", level)
|
||||||
|
}
|
||||||
|
allowedLevels[level] = true
|
||||||
|
}
|
||||||
|
return allowedLevels, nil
|
||||||
|
}
|
||||||
|
func validateOffLevel(allowedLevels map[LogLevel]bool) error {
|
||||||
|
if _, ok := allowedLevels[Off]; ok && len(allowedLevels) > 1 {
|
||||||
|
return errors.New("logLevel Off cant be mixed with other levels")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true, if log level is in allowed log levels list.
|
||||||
|
// If the list contains the only item 'common.Off' then IsAllowed will always return false for any input values.
|
||||||
|
func (listConstr *listConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
for l := range listConstr.allowedLevels {
|
||||||
|
if l == level && level != Off {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedLevels returns allowed levels configuration as a map.
|
||||||
|
func (listConstr *listConstraints) AllowedLevels() map[LogLevel]bool {
|
||||||
|
return listConstr.allowedLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================
|
||||||
|
|
||||||
|
type offConstraints struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOffConstraints() (*offConstraints, error) {
|
||||||
|
return &offConstraints{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (offConstr *offConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (offConstr *offConstraints) String() string {
|
||||||
|
return "Off constraint"
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var workingDir = "/"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err == nil {
|
||||||
|
workingDir = filepath.ToSlash(wd) + "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents runtime caller context.
|
||||||
|
type LogContextInterface interface {
|
||||||
|
// Caller's function name.
|
||||||
|
Func() string
|
||||||
|
// Caller's line number.
|
||||||
|
Line() int
|
||||||
|
// Caller's file short path (in slashed form).
|
||||||
|
ShortPath() string
|
||||||
|
// Caller's file full path (in slashed form).
|
||||||
|
FullPath() string
|
||||||
|
// Caller's file name (without path).
|
||||||
|
FileName() string
|
||||||
|
// True if the context is correct and may be used.
|
||||||
|
// If false, then an error in context evaluation occurred and
|
||||||
|
// all its other data may be corrupted.
|
||||||
|
IsValid() bool
|
||||||
|
// Time when log function was called.
|
||||||
|
CallTime() time.Time
|
||||||
|
// Custom context that can be set by calling logger.SetContext
|
||||||
|
CustomContext() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns context of the caller
|
||||||
|
func currentContext(custom interface{}) (LogContextInterface, error) {
|
||||||
|
return specifyContext(1, custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractCallerInfo(skip int) (fullPath string, shortPath string, funcName string, line int, err error) {
|
||||||
|
pc, fp, ln, ok := runtime.Caller(skip)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("error during runtime.Caller")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
line = ln
|
||||||
|
fullPath = fp
|
||||||
|
if strings.HasPrefix(fp, workingDir) {
|
||||||
|
shortPath = fp[len(workingDir):]
|
||||||
|
} else {
|
||||||
|
shortPath = fp
|
||||||
|
}
|
||||||
|
funcName = runtime.FuncForPC(pc).Name()
|
||||||
|
if strings.HasPrefix(funcName, workingDir) {
|
||||||
|
funcName = funcName[len(workingDir):]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns context of the function with placed "skip" stack frames of the caller
|
||||||
|
// If skip == 0 then behaves like currentContext
|
||||||
|
// Context is returned in any situation, even if error occurs. But, if an error
|
||||||
|
// occurs, the returned context is an error context, which contains no paths
|
||||||
|
// or names, but states that they can't be extracted.
|
||||||
|
func specifyContext(skip int, custom interface{}) (LogContextInterface, error) {
|
||||||
|
callTime := time.Now()
|
||||||
|
if skip < 0 {
|
||||||
|
err := fmt.Errorf("can not skip negative stack frames")
|
||||||
|
return &errorContext{callTime, err}, err
|
||||||
|
}
|
||||||
|
fullPath, shortPath, funcName, line, err := extractCallerInfo(skip + 2)
|
||||||
|
if err != nil {
|
||||||
|
return &errorContext{callTime, err}, err
|
||||||
|
}
|
||||||
|
_, fileName := filepath.Split(fullPath)
|
||||||
|
return &logContext{funcName, line, shortPath, fullPath, fileName, callTime, custom}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents a normal runtime caller context.
|
||||||
|
type logContext struct {
|
||||||
|
funcName string
|
||||||
|
line int
|
||||||
|
shortPath string
|
||||||
|
fullPath string
|
||||||
|
fileName string
|
||||||
|
callTime time.Time
|
||||||
|
custom interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) IsValid() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) Func() string {
|
||||||
|
return context.funcName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) Line() int {
|
||||||
|
return context.line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) ShortPath() string {
|
||||||
|
return context.shortPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) FullPath() string {
|
||||||
|
return context.fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) FileName() string {
|
||||||
|
return context.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) CallTime() time.Time {
|
||||||
|
return context.callTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) CustomContext() interface{} {
|
||||||
|
return context.custom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents an error context
|
||||||
|
type errorContext struct {
|
||||||
|
errorTime time.Time
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) getErrorText(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s() error: %s", prefix, errContext.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) IsValid() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) Line() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) Func() string {
|
||||||
|
return errContext.getErrorText("Func")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) ShortPath() string {
|
||||||
|
return errContext.getErrorText("ShortPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) FullPath() string {
|
||||||
|
return errContext.getErrorText("FullPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) FileName() string {
|
||||||
|
return errContext.getErrorText("FileName")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) CallTime() time.Time {
|
||||||
|
return errContext.errorTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) CustomContext() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Used in rules creation to validate input file and func filters
|
||||||
|
var (
|
||||||
|
fileFormatValidator = regexp.MustCompile(`[a-zA-Z0-9\\/ _\*\.]*`)
|
||||||
|
funcFormatValidator = regexp.MustCompile(`[a-zA-Z0-9_\*\.]*`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogLevelException represents an exceptional case used when you need some specific files or funcs to
|
||||||
|
// override general constraints and to use their own.
|
||||||
|
type LogLevelException struct {
|
||||||
|
funcPatternParts []string
|
||||||
|
filePatternParts []string
|
||||||
|
|
||||||
|
funcPattern string
|
||||||
|
filePattern string
|
||||||
|
|
||||||
|
constraints logLevelConstraints
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogLevelException creates a new exception.
|
||||||
|
func NewLogLevelException(funcPattern string, filePattern string, constraints logLevelConstraints) (*LogLevelException, error) {
|
||||||
|
if constraints == nil {
|
||||||
|
return nil, errors.New("constraints can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(LogLevelException)
|
||||||
|
|
||||||
|
err := exception.initFuncPatternParts(funcPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exception.funcPattern = strings.Join(exception.funcPatternParts, "")
|
||||||
|
|
||||||
|
err = exception.initFilePatternParts(filePattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exception.filePattern = strings.Join(exception.filePatternParts, "")
|
||||||
|
|
||||||
|
exception.constraints = constraints
|
||||||
|
|
||||||
|
return exception, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesContext returns true if context matches the patterns of this LogLevelException
|
||||||
|
func (logLevelEx *LogLevelException) MatchesContext(context LogContextInterface) bool {
|
||||||
|
return logLevelEx.match(context.Func(), context.FullPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true if log level is allowed according to the constraints of this LogLevelException
|
||||||
|
func (logLevelEx *LogLevelException) IsAllowed(level LogLevel) bool {
|
||||||
|
return logLevelEx.constraints.IsAllowed(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncPattern returns the function pattern of a exception
|
||||||
|
func (logLevelEx *LogLevelException) FuncPattern() string {
|
||||||
|
return logLevelEx.funcPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncPattern returns the file pattern of a exception
|
||||||
|
func (logLevelEx *LogLevelException) FilePattern() string {
|
||||||
|
return logLevelEx.filePattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// initFuncPatternParts checks whether the func filter has a correct format and splits funcPattern on parts
|
||||||
|
func (logLevelEx *LogLevelException) initFuncPatternParts(funcPattern string) (err error) {
|
||||||
|
|
||||||
|
if funcFormatValidator.FindString(funcPattern) != funcPattern {
|
||||||
|
return errors.New("func path \"" + funcPattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 _ * . allowed)")
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelEx.funcPatternParts = splitPattern(funcPattern)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether the file filter has a correct format and splits file patterns using splitPattern.
|
||||||
|
func (logLevelEx *LogLevelException) initFilePatternParts(filePattern string) (err error) {
|
||||||
|
|
||||||
|
if fileFormatValidator.FindString(filePattern) != filePattern {
|
||||||
|
return errors.New("file path \"" + filePattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 \\ / _ * . allowed)")
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelEx.filePatternParts = splitPattern(filePattern)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logLevelEx *LogLevelException) match(funcPath string, filePath string) bool {
|
||||||
|
if !stringMatchesPattern(logLevelEx.funcPatternParts, funcPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return stringMatchesPattern(logLevelEx.filePatternParts, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logLevelEx *LogLevelException) String() string {
|
||||||
|
str := fmt.Sprintf("Func: %s File: %s", logLevelEx.funcPattern, logLevelEx.filePattern)
|
||||||
|
|
||||||
|
if logLevelEx.constraints != nil {
|
||||||
|
str += fmt.Sprintf("Constr: %s", logLevelEx.constraints)
|
||||||
|
} else {
|
||||||
|
str += "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitPattern splits pattern into strings and asterisks. Example: "ab*cde**f" -> ["ab", "*", "cde", "*", "f"]
|
||||||
|
func splitPattern(pattern string) []string {
|
||||||
|
var patternParts []string
|
||||||
|
var lastChar rune
|
||||||
|
for _, char := range pattern {
|
||||||
|
if char == '*' {
|
||||||
|
if lastChar != '*' {
|
||||||
|
patternParts = append(patternParts, "*")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(patternParts) != 0 && lastChar != '*' {
|
||||||
|
patternParts[len(patternParts)-1] += string(char)
|
||||||
|
} else {
|
||||||
|
patternParts = append(patternParts, string(char))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastChar = char
|
||||||
|
}
|
||||||
|
|
||||||
|
return patternParts
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringMatchesPattern check whether testString matches pattern with asterisks.
|
||||||
|
// Standard regexp functionality is not used here because of performance issues.
|
||||||
|
func stringMatchesPattern(patternparts []string, testString string) bool {
|
||||||
|
if len(patternparts) == 0 {
|
||||||
|
return len(testString) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
part := patternparts[0]
|
||||||
|
if part != "*" {
|
||||||
|
index := strings.Index(testString, part)
|
||||||
|
if index == 0 {
|
||||||
|
return stringMatchesPattern(patternparts[1:], testString[len(part):])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(patternparts) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
newTestString := testString
|
||||||
|
part = patternparts[1]
|
||||||
|
for {
|
||||||
|
index := strings.Index(newTestString, part)
|
||||||
|
if index == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
newTestString = newTestString[index+len(part):]
|
||||||
|
result := stringMatchesPattern(patternparts[2:], newTestString)
|
||||||
|
if result {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// flusherInterface represents all objects that have to do cleanup
|
||||||
|
// at certain moments of time (e.g. before app shutdown to avoid data loss)
|
||||||
|
type flusherInterface interface {
|
||||||
|
Flush()
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// Log level type
|
||||||
|
type LogLevel uint8
|
||||||
|
|
||||||
|
// Log levels
|
||||||
|
const (
|
||||||
|
TraceLvl = iota
|
||||||
|
DebugLvl
|
||||||
|
InfoLvl
|
||||||
|
WarnLvl
|
||||||
|
ErrorLvl
|
||||||
|
CriticalLvl
|
||||||
|
Off
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log level string representations (used in configuration files)
|
||||||
|
const (
|
||||||
|
TraceStr = "trace"
|
||||||
|
DebugStr = "debug"
|
||||||
|
InfoStr = "info"
|
||||||
|
WarnStr = "warn"
|
||||||
|
ErrorStr = "error"
|
||||||
|
CriticalStr = "critical"
|
||||||
|
OffStr = "off"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelToStringRepresentations = map[LogLevel]string{
|
||||||
|
TraceLvl: TraceStr,
|
||||||
|
DebugLvl: DebugStr,
|
||||||
|
InfoLvl: InfoStr,
|
||||||
|
WarnLvl: WarnStr,
|
||||||
|
ErrorLvl: ErrorStr,
|
||||||
|
CriticalLvl: CriticalStr,
|
||||||
|
Off: OffStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelFromString parses a string and returns a corresponding log level, if sucessfull.
|
||||||
|
func LogLevelFromString(levelStr string) (level LogLevel, found bool) {
|
||||||
|
for lvl, lvlStr := range levelToStringRepresentations {
|
||||||
|
if lvlStr == levelStr {
|
||||||
|
return lvl, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelToString returns seelog string representation for a specified level. Returns "" for invalid log levels.
|
||||||
|
func (level LogLevel) String() string {
|
||||||
|
levelStr, ok := levelToStringRepresentations[level]
|
||||||
|
if ok {
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registeredReceivers = make(map[string]reflect.Type)
|
||||||
|
|
||||||
|
// RegisterReceiver records a custom receiver type, identified by a value
|
||||||
|
// of that type (second argument), under the specified name. Registered
|
||||||
|
// names can be used in the "name" attribute of <custom> config items.
|
||||||
|
//
|
||||||
|
// RegisterReceiver takes the type of the receiver argument, without taking
|
||||||
|
// the value into the account. So do NOT enter any data to the second argument
|
||||||
|
// and only call it like:
|
||||||
|
// RegisterReceiver("somename", &MyReceiverType{})
|
||||||
|
//
|
||||||
|
// After that, when a '<custom>' config tag with this name is used,
|
||||||
|
// a receiver of the specified type would be instantiated. Check
|
||||||
|
// CustomReceiver comments for interface details.
|
||||||
|
//
|
||||||
|
// NOTE 1: RegisterReceiver fails if you attempt to register different types
|
||||||
|
// with the same name.
|
||||||
|
//
|
||||||
|
// NOTE 2: RegisterReceiver registers those receivers that must be used in
|
||||||
|
// the configuration files (<custom> items). Basically it is just the way
|
||||||
|
// you tell seelog config parser what should it do when it meets a
|
||||||
|
// <custom> tag with a specific name and data attributes.
|
||||||
|
//
|
||||||
|
// But If you are only using seelog as a proxy to an already instantiated
|
||||||
|
// CustomReceiver (via LoggerFromCustomReceiver func), you should not call RegisterReceiver.
|
||||||
|
func RegisterReceiver(name string, receiver CustomReceiver) {
|
||||||
|
newType := reflect.TypeOf(reflect.ValueOf(receiver).Elem().Interface())
|
||||||
|
if t, ok := registeredReceivers[name]; ok && t != newType {
|
||||||
|
panic(fmt.Sprintf("duplicate types for %s: %s != %s", name, t, newType))
|
||||||
|
}
|
||||||
|
registeredReceivers[name] = newType
|
||||||
|
}
|
||||||
|
|
||||||
|
func customReceiverByName(name string) (creceiver CustomReceiver, err error) {
|
||||||
|
rt, ok := registeredReceivers[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("custom receiver name not registered: '%s'", name)
|
||||||
|
}
|
||||||
|
v, ok := reflect.New(rt).Interface().(CustomReceiver)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot instantiate receiver with name='%s'", name)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiverInitArgs represent arguments passed to the CustomReceiver.Init
|
||||||
|
// func when custom receiver is being initialized.
|
||||||
|
type CustomReceiverInitArgs struct {
|
||||||
|
// XmlCustomAttrs represent '<custom>' xml config item attributes that
|
||||||
|
// start with "data-". Map keys will be the attribute names without the "data-".
|
||||||
|
// Map values will the those attribute values.
|
||||||
|
//
|
||||||
|
// E.g. if you have a '<custom name="somename" data-attr1="a1" data-attr2="a2"/>'
|
||||||
|
// you will get map with 2 key-value pairs: "attr1"->"a1", "attr2"->"a2"
|
||||||
|
//
|
||||||
|
// Note that in custom items you can only use allowed attributes, like "name" and
|
||||||
|
// your custom attributes, starting with "data-". Any other will lead to a
|
||||||
|
// parsing error.
|
||||||
|
XmlCustomAttrs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver is the interface that external custom seelog message receivers
|
||||||
|
// must implement in order to be able to process seelog messages. Those receivers
|
||||||
|
// are set in the xml config file using the <custom> tag. Check receivers reference
|
||||||
|
// wiki section on that.
|
||||||
|
//
|
||||||
|
// Use seelog.RegisterReceiver on the receiver type before using it.
|
||||||
|
type CustomReceiver interface {
|
||||||
|
// ReceiveMessage is called when the custom receiver gets seelog message from
|
||||||
|
// a parent dispatcher.
|
||||||
|
//
|
||||||
|
// Message, level and context args represent all data that was included in the seelog
|
||||||
|
// message at the time it was logged.
|
||||||
|
//
|
||||||
|
// The formatting is already applied to the message and depends on the config
|
||||||
|
// like with any other receiver.
|
||||||
|
//
|
||||||
|
// If you would like to inform seelog of an error that happened during the handling of
|
||||||
|
// the message, return a non-nil error. This way you'll end up seeing your error like
|
||||||
|
// any other internal seelog error.
|
||||||
|
ReceiveMessage(message string, level LogLevel, context LogContextInterface) error
|
||||||
|
|
||||||
|
// AfterParse is called immediately after your custom receiver is instantiated by
|
||||||
|
// the xml config parser. So, if you need to do any startup logic after config parsing,
|
||||||
|
// like opening file or allocating any resources after the receiver is instantiated, do it here.
|
||||||
|
//
|
||||||
|
// If this func returns a non-nil error, then the loading procedure will fail. E.g.
|
||||||
|
// if you are loading a seelog xml config, the parser would not finish the loading
|
||||||
|
// procedure and inform about an error like with any other config error.
|
||||||
|
//
|
||||||
|
// If your custom logger needs some configuration, you can use custom attributes in
|
||||||
|
// your config. Check CustomReceiverInitArgs.XmlCustomAttrs comments.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This func is NOT called when the LoggerFromCustomReceiver func is used
|
||||||
|
// to create seelog proxy logger using the custom receiver. This func is only called when
|
||||||
|
// receiver is instantiated from a config.
|
||||||
|
AfterParse(initArgs CustomReceiverInitArgs) error
|
||||||
|
|
||||||
|
// Flush is called when the custom receiver gets a 'flush' directive from a
|
||||||
|
// parent receiver. If custom receiver implements some kind of buffering or
|
||||||
|
// queing, then the appropriate reaction on a flush message is synchronous
|
||||||
|
// flushing of all those queues/buffers. If custom receiver doesn't have
|
||||||
|
// such mechanisms, then flush implementation may be left empty.
|
||||||
|
Flush()
|
||||||
|
|
||||||
|
// Close is called when the custom receiver gets a 'close' directive from a
|
||||||
|
// parent receiver. This happens when a top-level seelog dispatcher is sending
|
||||||
|
// 'close' to all child nodes and it means that current seelog logger is being closed.
|
||||||
|
// If you need to do any cleanup after your custom receiver is done, you should do
|
||||||
|
// it here.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type customReceiverDispatcher struct {
|
||||||
|
formatter *formatter
|
||||||
|
innerReceiver CustomReceiver
|
||||||
|
customReceiverName string
|
||||||
|
usedArgs CustomReceiverInitArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomReceiverDispatcher creates a customReceiverDispatcher which dispatches data to a specific receiver created
|
||||||
|
// using a <custom> tag in the config file.
|
||||||
|
func NewCustomReceiverDispatcher(formatter *formatter, customReceiverName string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if len(customReceiverName) == 0 {
|
||||||
|
return nil, errors.New("custom receiver name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
creceiver, err := customReceiverByName(customReceiverName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = creceiver.AfterParse(cArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
disp := &customReceiverDispatcher{formatter, creceiver, customReceiverName, cArgs}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomReceiverDispatcherByValue is basically the same as NewCustomReceiverDispatcher, but using
|
||||||
|
// a specific CustomReceiver value instead of instantiating a new one by type.
|
||||||
|
func NewCustomReceiverDispatcherByValue(formatter *formatter, customReceiver CustomReceiver, name string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if customReceiver == nil {
|
||||||
|
return nil, errors.New("customReceiver cannot be nil")
|
||||||
|
}
|
||||||
|
disp := &customReceiverDispatcher{formatter, customReceiver, name, cArgs}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
errorFunc(fmt.Errorf("panic in custom receiver '%s'.Dispatch: %s", reflect.TypeOf(disp.innerReceiver), err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := disp.innerReceiver.ReceiveMessage(disp.formatter.Format(message, level, context), level, context)
|
||||||
|
if err != nil {
|
||||||
|
errorFunc(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Flush() {
|
||||||
|
disp.innerReceiver.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Close() error {
|
||||||
|
disp.innerReceiver.Flush()
|
||||||
|
|
||||||
|
err := disp.innerReceiver.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *customReceiverDispatcher) String() string {
|
||||||
|
datas := ""
|
||||||
|
skeys := make([]string, 0, len(disp.usedArgs.XmlCustomAttrs))
|
||||||
|
for i := range disp.usedArgs.XmlCustomAttrs {
|
||||||
|
skeys = append(skeys, i)
|
||||||
|
}
|
||||||
|
sort.Strings(skeys)
|
||||||
|
for _, key := range skeys {
|
||||||
|
datas += fmt.Sprintf("<%s, %s> ", key, disp.usedArgs.XmlCustomAttrs[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("Custom receiver %s [fmt='%s'],[data='%s'],[inner='%s']\n",
|
||||||
|
disp.customReceiverName, disp.formatter.String(), datas, disp.innerReceiver)
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A dispatcherInterface is used to dispatch message to all underlying receivers.
|
||||||
|
// Dispatch logic depends on given context and log level. Any errors are reported using errorFunc.
|
||||||
|
// Also, as underlying receivers may have a state, dispatcher has a ShuttingDown method which performs
|
||||||
|
// an immediate cleanup of all data that is stored in the receivers
|
||||||
|
type dispatcherInterface interface {
|
||||||
|
flusherInterface
|
||||||
|
io.Closer
|
||||||
|
Dispatch(message string, level LogLevel, context LogContextInterface, errorFunc func(err error))
|
||||||
|
}
|
||||||
|
|
||||||
|
type dispatcher struct {
|
||||||
|
formatter *formatter
|
||||||
|
writers []*formattedWriter
|
||||||
|
dispatchers []dispatcherInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a dispatcher which dispatches data to a list of receivers.
|
||||||
|
// Each receiver should be either a Dispatcher or io.Writer, otherwise an error will be returned
|
||||||
|
func createDispatcher(formatter *formatter, receivers []interface{}) (*dispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if receivers == nil || len(receivers) == 0 {
|
||||||
|
return nil, errors.New("receivers cannot be nil or empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
disp := &dispatcher{formatter, make([]*formattedWriter, 0), make([]dispatcherInterface, 0)}
|
||||||
|
for _, receiver := range receivers {
|
||||||
|
writer, ok := receiver.(*formattedWriter)
|
||||||
|
if ok {
|
||||||
|
disp.writers = append(disp.writers, writer)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ioWriter, ok := receiver.(io.Writer)
|
||||||
|
if ok {
|
||||||
|
writer, err := NewFormattedWriter(ioWriter, disp.formatter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
disp.writers = append(disp.writers, writer)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dispInterface, ok := receiver.(dispatcherInterface)
|
||||||
|
if ok {
|
||||||
|
disp.dispatchers = append(disp.dispatchers, dispInterface)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("method can receive either io.Writer or dispatcherInterface")
|
||||||
|
}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
|
||||||
|
for _, writer := range disp.writers {
|
||||||
|
err := writer.Write(message, level, context)
|
||||||
|
if err != nil {
|
||||||
|
errorFunc(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dispInterface := range disp.dispatchers {
|
||||||
|
dispInterface.Dispatch(message, level, context, errorFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush goes through all underlying writers which implement flusherInterface interface
|
||||||
|
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||||
|
func (disp *dispatcher) Flush() {
|
||||||
|
for _, disp := range disp.Dispatchers() {
|
||||||
|
disp.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatWriter := range disp.Writers() {
|
||||||
|
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||||
|
if ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close goes through all underlying writers which implement io.Closer interface
|
||||||
|
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||||
|
// Before closing, writers are flushed to prevent loss of any buffered data, so
|
||||||
|
// a call to Flush() func before Close() is not necessary
|
||||||
|
func (disp *dispatcher) Close() error {
|
||||||
|
for _, disp := range disp.Dispatchers() {
|
||||||
|
disp.Flush()
|
||||||
|
err := disp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatWriter := range disp.Writers() {
|
||||||
|
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||||
|
if ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
closer, ok := formatWriter.Writer().(io.Closer)
|
||||||
|
if ok {
|
||||||
|
err := closer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Writers() []*formattedWriter {
|
||||||
|
return disp.writers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Dispatchers() []dispatcherInterface {
|
||||||
|
return disp.dispatchers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) String() string {
|
||||||
|
str := "formatter: " + disp.formatter.String() + "\n"
|
||||||
|
|
||||||
|
str += " ->Dispatchers:"
|
||||||
|
|
||||||
|
if len(disp.dispatchers) == 0 {
|
||||||
|
str += "none\n"
|
||||||
|
} else {
|
||||||
|
str += "\n"
|
||||||
|
|
||||||
|
for _, disp := range disp.dispatchers {
|
||||||
|
str += fmt.Sprintf(" ->%s", disp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str += " ->Writers:"
|
||||||
|
|
||||||
|
if len(disp.writers) == 0 {
|
||||||
|
str += "none\n"
|
||||||
|
} else {
|
||||||
|
str += "\n"
|
||||||
|
|
||||||
|
for _, writer := range disp.writers {
|
||||||
|
str += fmt.Sprintf(" ->%s\n", writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A filterDispatcher writes the given message to underlying receivers only if message log level
|
||||||
|
// is in the allowed list.
|
||||||
|
type filterDispatcher struct {
|
||||||
|
*dispatcher
|
||||||
|
allowList map[LogLevel]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilterDispatcher creates a new filterDispatcher using a list of allowed levels.
|
||||||
|
func NewFilterDispatcher(formatter *formatter, receivers []interface{}, allowList ...LogLevel) (*filterDispatcher, error) {
|
||||||
|
disp, err := createDispatcher(formatter, receivers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allows := make(map[LogLevel]bool)
|
||||||
|
for _, allowLevel := range allowList {
|
||||||
|
allows[allowLevel] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &filterDispatcher{disp, allows}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterDispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
isAllowed, ok := filter.allowList[level]
|
||||||
|
if ok && isAllowed {
|
||||||
|
filter.dispatcher.Dispatch(message, level, context, errorFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterDispatcher) String() string {
|
||||||
|
return fmt.Sprintf("filterDispatcher ->\n%s", filter.dispatcher)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A splitDispatcher just writes the given message to underlying receivers. (Splits the message stream.)
|
||||||
|
type splitDispatcher struct {
|
||||||
|
*dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSplitDispatcher(formatter *formatter, receivers []interface{}) (*splitDispatcher, error) {
|
||||||
|
disp, err := createDispatcher(formatter, receivers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &splitDispatcher{disp}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (splitter *splitDispatcher) String() string {
|
||||||
|
return fmt.Sprintf("splitDispatcher ->\n%s", splitter.dispatcher.String())
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) 2014 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package seelog implements logging functionality with flexible dispatching, filtering, and formatting.
|
||||||
|
|
||||||
|
Creation
|
||||||
|
|
||||||
|
To create a logger, use one of the following constructors:
|
||||||
|
func LoggerFromConfigAsBytes
|
||||||
|
func LoggerFromConfigAsFile
|
||||||
|
func LoggerFromConfigAsString
|
||||||
|
func LoggerFromWriterWithMinLevel
|
||||||
|
func LoggerFromWriterWithMinLevelAndFormat
|
||||||
|
func LoggerFromCustomReceiver (check https://github.com/cihub/seelog/wiki/Custom-receivers)
|
||||||
|
Example:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Flush()
|
||||||
|
... use logger ...
|
||||||
|
}
|
||||||
|
The "defer" line is important because if you are using asynchronous logger behavior, without this line you may end up losing some
|
||||||
|
messages when you close your application because they are processed in another non-blocking goroutine. To avoid that you
|
||||||
|
explicitly defer flushing all messages before closing.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
Logger created using one of the LoggerFrom* funcs can be used directly by calling one of the main log funcs.
|
||||||
|
Example:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Flush()
|
||||||
|
logger.Trace("test")
|
||||||
|
logger.Debugf("var = %s", "abc")
|
||||||
|
}
|
||||||
|
|
||||||
|
Having loggers as variables is convenient if you are writing your own package with internal logging or if you have
|
||||||
|
several loggers with different options.
|
||||||
|
But for most standalone apps it is more convenient to use package level funcs and vars. There is a package level
|
||||||
|
var 'Current' made for it. You can replace it with another logger using 'ReplaceLogger' and then use package level funcs:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.ReplaceLogger(logger)
|
||||||
|
defer log.Flush()
|
||||||
|
log.Trace("test")
|
||||||
|
log.Debugf("var = %s", "abc")
|
||||||
|
}
|
||||||
|
Last lines
|
||||||
|
log.Trace("test")
|
||||||
|
log.Debugf("var = %s", "abc")
|
||||||
|
do the same as
|
||||||
|
log.Current.Trace("test")
|
||||||
|
log.Current.Debugf("var = %s", "abc")
|
||||||
|
In this example the 'Current' logger was replaced using a 'ReplaceLogger' call and became equal to 'logger' variable created from config.
|
||||||
|
This way you are able to use package level funcs instead of passing the logger variable.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
|
||||||
|
Main seelog point is to configure logger via config files and not the code.
|
||||||
|
The configuration is read by LoggerFrom* funcs. These funcs read xml configuration from different sources and try
|
||||||
|
to create a logger using it.
|
||||||
|
|
||||||
|
All the configuration features are covered in detail in the official wiki: https://github.com/cihub/seelog/wiki.
|
||||||
|
There are many sections covering different aspects of seelog, but the most important for understanding configs are:
|
||||||
|
https://github.com/cihub/seelog/wiki/Constraints-and-exceptions
|
||||||
|
https://github.com/cihub/seelog/wiki/Dispatchers-and-receivers
|
||||||
|
https://github.com/cihub/seelog/wiki/Formatting
|
||||||
|
https://github.com/cihub/seelog/wiki/Logger-types
|
||||||
|
After you understand these concepts, check the 'Reference' section on the main wiki page to get the up-to-date
|
||||||
|
list of dispatchers, receivers, formats, and logger types.
|
||||||
|
|
||||||
|
Here is an example config with all these features:
|
||||||
|
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000" critmsgcount="500" minlevel="debug">
|
||||||
|
<exceptions>
|
||||||
|
<exception filepattern="test*" minlevel="error"/>
|
||||||
|
</exceptions>
|
||||||
|
<outputs formatid="all">
|
||||||
|
<file path="all.log"/>
|
||||||
|
<filter levels="info">
|
||||||
|
<console formatid="fmtinfo"/>
|
||||||
|
</filter>
|
||||||
|
<filter levels="error,critical" formatid="fmterror">
|
||||||
|
<console/>
|
||||||
|
<file path="errors.log"/>
|
||||||
|
</filter>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="fmtinfo" format="[%Level] [%Time] %Msg%n"/>
|
||||||
|
<format id="fmterror" format="[%LEVEL] [%Time] [%FuncShort @ %File.%Line] %Msg%n"/>
|
||||||
|
<format id="all" format="[%Level] [%Time] [@ %File.%Line] %Msg%n"/>
|
||||||
|
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
|
||||||
|
</formats>
|
||||||
|
</seelog>
|
||||||
|
This config represents a logger with adaptive timeout between log messages (check logger types reference) which
|
||||||
|
logs to console, all.log, and errors.log depending on the log level. Its output formats also depend on log level. This logger will only
|
||||||
|
use log level 'debug' and higher (minlevel is set) for all files with names that don't start with 'test'. For files starting with 'test'
|
||||||
|
this logger prohibits all levels below 'error'.
|
||||||
|
|
||||||
|
Configuration using code
|
||||||
|
|
||||||
|
Although configuration using code is not recommended, it is sometimes needed and it is possible to do with seelog. Basically, what
|
||||||
|
you need to do to get started is to create constraints, exceptions and a dispatcher tree (same as with config). Most of the New*
|
||||||
|
functions in this package are used to provide such capabilities.
|
||||||
|
|
||||||
|
Here is an example of configuration in code, that demonstrates an async loop logger that logs to a simple split dispatcher with
|
||||||
|
a console receiver using a specified format and is filtered using a top-level min-max constraints and one expection for
|
||||||
|
the 'main.go' file. So, this is basically a demonstration of configuration of most of the features:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer log.Flush()
|
||||||
|
log.Info("Hello from Seelog!")
|
||||||
|
|
||||||
|
consoleWriter, _ := log.NewConsoleWriter()
|
||||||
|
formatter, _ := log.NewFormatter("%Level %Msg %File%n")
|
||||||
|
root, _ := log.NewSplitDispatcher(formatter, []interface{}{consoleWriter})
|
||||||
|
constraints, _ := log.NewMinMaxConstraints(log.TraceLvl, log.CriticalLvl)
|
||||||
|
specificConstraints, _ := log.NewListConstraints([]log.LogLevel{log.InfoLvl, log.ErrorLvl})
|
||||||
|
ex, _ := log.NewLogLevelException("*", "*main.go", specificConstraints)
|
||||||
|
exceptions := []*log.LogLevelException{ex}
|
||||||
|
|
||||||
|
logger := log.NewAsyncLoopLogger(log.NewLoggerConfig(constraints, exceptions, root))
|
||||||
|
log.ReplaceLogger(logger)
|
||||||
|
|
||||||
|
log.Trace("This should not be seen")
|
||||||
|
log.Debug("This should not be seen")
|
||||||
|
log.Info("Test")
|
||||||
|
log.Error("Test2")
|
||||||
|
}
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
To learn seelog features faster you should check the examples package: https://github.com/cihub/seelog-examples
|
||||||
|
It contains many example configs and usecases.
|
||||||
|
*/
|
||||||
|
package seelog
|
|
@ -0,0 +1,466 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatterSymbol is a special symbol used in config files to mark special format aliases.
|
||||||
|
const (
|
||||||
|
FormatterSymbol = '%'
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
formatterParameterStart = '('
|
||||||
|
formatterParameterEnd = ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time and date formats used for %Date and %Time aliases.
|
||||||
|
const (
|
||||||
|
DateDefaultFormat = "2006-01-02"
|
||||||
|
TimeFormat = "15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultMsgFormat = "%Ns [%Level] %Msg%n"
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultFormatter *formatter
|
||||||
|
msgonlyformatter *formatter
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
if DefaultFormatter, err = NewFormatter(DefaultMsgFormat); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("error during creating DefaultFormatter: %s", err))
|
||||||
|
}
|
||||||
|
if msgonlyformatter, err = NewFormatter("%Msg"); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("error during creating msgonlyformatter: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatterFunc represents one formatter object that starts with '%' sign in the 'format' attribute
|
||||||
|
// of the 'format' config item. These special symbols are replaced with context values or special
|
||||||
|
// strings when message is written to byte receiver.
|
||||||
|
//
|
||||||
|
// Check https://github.com/cihub/seelog/wiki/Formatting for details.
|
||||||
|
// Full list (with descriptions) of formatters: https://github.com/cihub/seelog/wiki/Format-reference
|
||||||
|
//
|
||||||
|
// FormatterFunc takes raw log message, level, log context and returns a string, number (of any type) or any object
|
||||||
|
// that can be evaluated as string.
|
||||||
|
type FormatterFunc func(message string, level LogLevel, context LogContextInterface) interface{}
|
||||||
|
|
||||||
|
// FormatterFuncCreator is a factory of FormatterFunc objects. It is used to generate parameterized
|
||||||
|
// formatters (such as %Date or %EscM) and custom user formatters.
|
||||||
|
type FormatterFuncCreator func(param string) FormatterFunc
|
||||||
|
|
||||||
|
var formatterFuncs = map[string]FormatterFunc{
|
||||||
|
"Level": formatterLevel,
|
||||||
|
"Lev": formatterLev,
|
||||||
|
"LEVEL": formatterLEVEL,
|
||||||
|
"LEV": formatterLEV,
|
||||||
|
"l": formatterl,
|
||||||
|
"Msg": formatterMsg,
|
||||||
|
"FullPath": formatterFullPath,
|
||||||
|
"File": formatterFile,
|
||||||
|
"RelFile": formatterRelFile,
|
||||||
|
"Func": FormatterFunction,
|
||||||
|
"FuncShort": FormatterFunctionShort,
|
||||||
|
"Line": formatterLine,
|
||||||
|
"Time": formatterTime,
|
||||||
|
"UTCTime": formatterUTCTime,
|
||||||
|
"Ns": formatterNs,
|
||||||
|
"UTCNs": formatterUTCNs,
|
||||||
|
"r": formatterr,
|
||||||
|
"n": formattern,
|
||||||
|
"t": formattert,
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatterFuncsParameterized = map[string]FormatterFuncCreator{
|
||||||
|
"Date": createDateTimeFormatterFunc,
|
||||||
|
"UTCDate": createUTCDateTimeFormatterFunc,
|
||||||
|
"EscM": createANSIEscapeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorAliasReserved(name string) error {
|
||||||
|
return fmt.Errorf("cannot use '%s' as custom formatter name. Name is reserved", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCustomFormatter registers a new custom formatter factory with a given name. If returned error is nil,
|
||||||
|
// then this name (prepended by '%' symbol) can be used in 'format' attributes in configuration and
|
||||||
|
// it will be treated like the standard parameterized formatter identifiers.
|
||||||
|
//
|
||||||
|
// RegisterCustomFormatter needs to be called before creating a logger for it to take effect. The general recommendation
|
||||||
|
// is to call it once in 'init' func of your application or any initializer func.
|
||||||
|
//
|
||||||
|
// For usage examples, check https://github.com/cihub/seelog/wiki/Custom-formatters.
|
||||||
|
//
|
||||||
|
// Name must only consist of letters (unicode.IsLetter).
|
||||||
|
//
|
||||||
|
// Name must not be one of the already registered standard formatter names
|
||||||
|
// (https://github.com/cihub/seelog/wiki/Format-reference) and previously registered
|
||||||
|
// custom format names. To avoid any potential name conflicts (in future releases), it is recommended
|
||||||
|
// to start your custom formatter name with a namespace (e.g. 'MyCompanySomething') or a 'Custom' keyword.
|
||||||
|
func RegisterCustomFormatter(name string, creator FormatterFuncCreator) error {
|
||||||
|
if _, ok := formatterFuncs[name]; ok {
|
||||||
|
return errorAliasReserved(name)
|
||||||
|
}
|
||||||
|
if _, ok := formatterFuncsParameterized[name]; ok {
|
||||||
|
return errorAliasReserved(name)
|
||||||
|
}
|
||||||
|
formatterFuncsParameterized[name] = creator
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatter is used to write messages in a specific format, inserting such additional data
|
||||||
|
// as log level, date/time, etc.
|
||||||
|
type formatter struct {
|
||||||
|
fmtStringOriginal string
|
||||||
|
fmtString string
|
||||||
|
formatterFuncs []FormatterFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormatter creates a new formatter using a format string
|
||||||
|
func NewFormatter(formatString string) (*formatter, error) {
|
||||||
|
fmtr := new(formatter)
|
||||||
|
fmtr.fmtStringOriginal = formatString
|
||||||
|
if err := buildFormatterFuncs(fmtr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fmtr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFormatterFuncs(formatter *formatter) error {
|
||||||
|
var (
|
||||||
|
fsbuf = new(bytes.Buffer)
|
||||||
|
fsolm1 = len(formatter.fmtStringOriginal) - 1
|
||||||
|
)
|
||||||
|
for i := 0; i <= fsolm1; i++ {
|
||||||
|
if char := formatter.fmtStringOriginal[i]; char != FormatterSymbol {
|
||||||
|
fsbuf.WriteByte(char)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the index is at the end of the string.
|
||||||
|
if i == fsolm1 {
|
||||||
|
return fmt.Errorf("format error: %c cannot be last symbol", FormatterSymbol)
|
||||||
|
}
|
||||||
|
// Check if the formatter symbol is doubled and skip it as nonmatching.
|
||||||
|
if formatter.fmtStringOriginal[i+1] == FormatterSymbol {
|
||||||
|
fsbuf.WriteRune(FormatterSymbol)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
function, ni, err := formatter.extractFormatterFunc(i + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Append formatting string "%v".
|
||||||
|
fsbuf.Write([]byte{37, 118})
|
||||||
|
i = ni
|
||||||
|
formatter.formatterFuncs = append(formatter.formatterFuncs, function)
|
||||||
|
}
|
||||||
|
formatter.fmtString = fsbuf.String()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) extractFormatterFunc(index int) (FormatterFunc, int, error) {
|
||||||
|
letterSequence := formatter.extractLetterSequence(index)
|
||||||
|
if len(letterSequence) == 0 {
|
||||||
|
return nil, 0, fmt.Errorf("format error: lack of formatter after %c at %d", FormatterSymbol, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function, formatterLength, ok := formatter.findFormatterFunc(letterSequence)
|
||||||
|
if ok {
|
||||||
|
return function, index + formatterLength - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function, formatterLength, ok, err := formatter.findFormatterFuncParametrized(letterSequence, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return function, index + formatterLength - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, errors.New("format error: unrecognized formatter at " + strconv.Itoa(index) + ": " + letterSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) extractLetterSequence(index int) string {
|
||||||
|
letters := ""
|
||||||
|
|
||||||
|
bytesToParse := []byte(formatter.fmtStringOriginal[index:])
|
||||||
|
runeCount := utf8.RuneCount(bytesToParse)
|
||||||
|
for i := 0; i < runeCount; i++ {
|
||||||
|
rune, runeSize := utf8.DecodeRune(bytesToParse)
|
||||||
|
bytesToParse = bytesToParse[runeSize:]
|
||||||
|
|
||||||
|
if unicode.IsLetter(rune) {
|
||||||
|
letters += string(rune)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return letters
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findFormatterFunc(letters string) (FormatterFunc, int, bool) {
|
||||||
|
currentVerb := letters
|
||||||
|
for i := 0; i < len(letters); i++ {
|
||||||
|
function, ok := formatterFuncs[currentVerb]
|
||||||
|
if ok {
|
||||||
|
return function, len(currentVerb), ok
|
||||||
|
}
|
||||||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findFormatterFuncParametrized(letters string, lettersStartIndex int) (FormatterFunc, int, bool, error) {
|
||||||
|
currentVerb := letters
|
||||||
|
for i := 0; i < len(letters); i++ {
|
||||||
|
functionCreator, ok := formatterFuncsParameterized[currentVerb]
|
||||||
|
if ok {
|
||||||
|
parameter := ""
|
||||||
|
parameterLen := 0
|
||||||
|
isVerbEqualsLetters := i == 0 // if not, then letter goes after formatter, and formatter is parameterless
|
||||||
|
if isVerbEqualsLetters {
|
||||||
|
userParameter := ""
|
||||||
|
var err error
|
||||||
|
userParameter, parameterLen, ok, err = formatter.findparameter(lettersStartIndex + len(currentVerb))
|
||||||
|
if ok {
|
||||||
|
parameter = userParameter
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, 0, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return functionCreator(parameter), len(currentVerb) + parameterLen, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findparameter(startIndex int) (string, int, bool, error) {
|
||||||
|
if len(formatter.fmtStringOriginal) == startIndex || formatter.fmtStringOriginal[startIndex] != formatterParameterStart {
|
||||||
|
return "", 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endIndex := strings.Index(formatter.fmtStringOriginal[startIndex:], string(formatterParameterEnd))
|
||||||
|
if endIndex == -1 {
|
||||||
|
return "", 0, false, fmt.Errorf("Unmatched parenthesis or invalid parameter at %d: %s",
|
||||||
|
startIndex, formatter.fmtStringOriginal[startIndex:])
|
||||||
|
}
|
||||||
|
endIndex += startIndex
|
||||||
|
|
||||||
|
length := endIndex - startIndex + 1
|
||||||
|
|
||||||
|
return formatter.fmtStringOriginal[startIndex+1 : endIndex], length, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format processes a message with special formatters, log level, and context. Returns formatted string
|
||||||
|
// with all formatter identifiers changed to appropriate values.
|
||||||
|
func (formatter *formatter) Format(message string, level LogLevel, context LogContextInterface) string {
|
||||||
|
if len(formatter.formatterFuncs) == 0 {
|
||||||
|
return formatter.fmtString
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make([]interface{}, len(formatter.formatterFuncs))
|
||||||
|
for i, function := range formatter.formatterFuncs {
|
||||||
|
params[i] = function(message, level, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(formatter.fmtString, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) String() string {
|
||||||
|
return formatter.fmtStringOriginal
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================================================
|
||||||
|
|
||||||
|
const (
|
||||||
|
wrongLogLevel = "WRONG_LOGLEVEL"
|
||||||
|
wrongEscapeCode = "WRONG_ESCAPE"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelToString = map[LogLevel]string{
|
||||||
|
TraceLvl: "Trace",
|
||||||
|
DebugLvl: "Debug",
|
||||||
|
InfoLvl: "Info",
|
||||||
|
WarnLvl: "Warn",
|
||||||
|
ErrorLvl: "Error",
|
||||||
|
CriticalLvl: "Critical",
|
||||||
|
Off: "Off",
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelToShortString = map[LogLevel]string{
|
||||||
|
TraceLvl: "Trc",
|
||||||
|
DebugLvl: "Dbg",
|
||||||
|
InfoLvl: "Inf",
|
||||||
|
WarnLvl: "Wrn",
|
||||||
|
ErrorLvl: "Err",
|
||||||
|
CriticalLvl: "Crt",
|
||||||
|
Off: "Off",
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelToShortestString = map[LogLevel]string{
|
||||||
|
TraceLvl: "t",
|
||||||
|
DebugLvl: "d",
|
||||||
|
InfoLvl: "i",
|
||||||
|
WarnLvl: "w",
|
||||||
|
ErrorLvl: "e",
|
||||||
|
CriticalLvl: "c",
|
||||||
|
Off: "o",
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLevel(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLev(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToShortString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLEVEL(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return strings.ToTitle(formatterLevel(message, level, context).(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLEV(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return strings.ToTitle(formatterLev(message, level, context).(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterl(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToShortestString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterMsg(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterFullPath(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.FullPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.FileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterRelFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.ShortPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatterFunction(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.Func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatterFunctionShort(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
f := context.Func()
|
||||||
|
spl := strings.Split(f, ".")
|
||||||
|
return spl[len(spl)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLine(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.Line()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().Format(TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterUTCTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().Format(TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterUTCNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterr(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\r"
|
||||||
|
}
|
||||||
|
|
||||||
|
func formattern(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func formattert(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||||
|
format := dateTimeFormat
|
||||||
|
if format == "" {
|
||||||
|
format = DateDefaultFormat
|
||||||
|
}
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().Format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUTCDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||||
|
format := dateTimeFormat
|
||||||
|
if format == "" {
|
||||||
|
format = DateDefaultFormat
|
||||||
|
}
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().Format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createANSIEscapeFunc(escapeCodeString string) FormatterFunc {
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
if len(escapeCodeString) == 0 {
|
||||||
|
return wrongEscapeCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%c[%sm", 0x1B, escapeCodeString)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// Base struct for custom errors.
|
||||||
|
type baseError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be baseError) Error() string {
|
||||||
|
return be.message
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File and directory permitions.
|
||||||
|
const (
|
||||||
|
defaultFilePermissions = 0666
|
||||||
|
defaultDirectoryPermissions = 0767
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Max number of directories can be read asynchronously.
|
||||||
|
maxDirNumberReadAsync = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
type cannotOpenFileError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCannotOpenFileError(fname string) *cannotOpenFileError {
|
||||||
|
return &cannotOpenFileError{baseError{message: "Cannot open file: " + fname}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notDirectoryError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNotDirectoryError(dname string) *notDirectoryError {
|
||||||
|
return ¬DirectoryError{baseError{message: dname + " is not directory"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileFilter is a filtering criteria function for '*os.File'.
|
||||||
|
// Must return 'false' to set aside the given file.
|
||||||
|
type fileFilter func(os.FileInfo, *os.File) bool
|
||||||
|
|
||||||
|
// filePathFilter is a filtering creteria function for file path.
|
||||||
|
// Must return 'false' to set aside the given file.
|
||||||
|
type filePathFilter func(filePath string) bool
|
||||||
|
|
||||||
|
// GetSubdirNames returns a list of directories found in
|
||||||
|
// the given one with dirPath.
|
||||||
|
func getSubdirNames(dirPath string) ([]string, error) {
|
||||||
|
fi, err := os.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return nil, newNotDirectoryError(dirPath)
|
||||||
|
}
|
||||||
|
dd, err := os.Open(dirPath)
|
||||||
|
// Cannot open file.
|
||||||
|
if err != nil {
|
||||||
|
if dd != nil {
|
||||||
|
dd.Close()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dd.Close()
|
||||||
|
// TODO: Improve performance by buffering reading.
|
||||||
|
allEntities, err := dd.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subDirs := []string{}
|
||||||
|
for _, entity := range allEntities {
|
||||||
|
if entity.IsDir() {
|
||||||
|
subDirs = append(subDirs, entity.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subDirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubdirAbsPaths recursively visit all the subdirectories
|
||||||
|
// starting from the given directory and returns absolute paths for them.
|
||||||
|
func getAllSubdirAbsPaths(dirPath string) (res []string, err error) {
|
||||||
|
dps, err := getSubdirAbsPaths(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
res = []string{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = append(res, dps...)
|
||||||
|
for _, dp := range dps {
|
||||||
|
sdps, err := getAllSubdirAbsPaths(dp)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
res = append(res, sdps...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubdirAbsPaths supplies absolute paths for all subdirectiries in a given directory.
|
||||||
|
// Input: (I1) dirPath - absolute path of a directory in question.
|
||||||
|
// Out: (O1) - slice of subdir asbolute paths; (O2) - error of the operation.
|
||||||
|
// Remark: If error (O2) is non-nil then (O1) is nil and vice versa.
|
||||||
|
func getSubdirAbsPaths(dirPath string) ([]string, error) {
|
||||||
|
sdns, err := getSubdirNames(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rsdns := []string{}
|
||||||
|
for _, sdn := range sdns {
|
||||||
|
rsdns = append(rsdns, filepath.Join(dirPath, sdn))
|
||||||
|
}
|
||||||
|
return rsdns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpenFilesInDir supplies a slice of os.File pointers to files located in the directory.
|
||||||
|
// Remark: Ignores files for which fileFilter returns false
|
||||||
|
func getOpenFilesInDir(dirPath string, fFilter fileFilter) ([]*os.File, error) {
|
||||||
|
dfi, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||||
|
}
|
||||||
|
defer dfi.Close()
|
||||||
|
// Size of read buffer (i.e. chunk of items read at a time).
|
||||||
|
rbs := 64
|
||||||
|
resFiles := []*os.File{}
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
// Read directory entities by reasonable chuncks
|
||||||
|
// to prevent overflows on big number of files.
|
||||||
|
fis, e := dfi.Readdir(rbs)
|
||||||
|
switch e {
|
||||||
|
// It's OK.
|
||||||
|
case nil:
|
||||||
|
// Do nothing, just continue cycle.
|
||||||
|
case io.EOF:
|
||||||
|
break L
|
||||||
|
// Something went wrong.
|
||||||
|
default:
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// THINK: Maybe, use async running.
|
||||||
|
for _, fi := range fis {
|
||||||
|
// NB: On Linux this could be a problem as
|
||||||
|
// there are lots of file types available.
|
||||||
|
if !fi.IsDir() {
|
||||||
|
f, e := os.Open(filepath.Join(dirPath, fi.Name()))
|
||||||
|
if e != nil {
|
||||||
|
if f != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
// THINK: Add nil as indicator that a problem occurred.
|
||||||
|
resFiles = append(resFiles, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check filter condition.
|
||||||
|
if fFilter != nil && !fFilter(fi, f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resFiles = append(resFiles, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRegular(m os.FileMode) bool {
|
||||||
|
return m&os.ModeType == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDirFilePaths return full paths of the files located in the directory.
|
||||||
|
// Remark: Ignores files for which fileFilter returns false.
|
||||||
|
func getDirFilePaths(dirPath string, fpFilter filePathFilter, pathIsName bool) ([]string, error) {
|
||||||
|
dfi, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||||
|
}
|
||||||
|
defer dfi.Close()
|
||||||
|
|
||||||
|
var absDirPath string
|
||||||
|
if !filepath.IsAbs(dirPath) {
|
||||||
|
absDirPath, err = filepath.Abs(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get absolute path of directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
absDirPath = dirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if dirPath is really directory.
|
||||||
|
// Size of read buffer (i.e. chunk of items read at a time).
|
||||||
|
rbs := 2 << 5
|
||||||
|
filePaths := []string{}
|
||||||
|
|
||||||
|
var fp string
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
// Read directory entities by reasonable chuncks
|
||||||
|
// to prevent overflows on big number of files.
|
||||||
|
fis, e := dfi.Readdir(rbs)
|
||||||
|
switch e {
|
||||||
|
// It's OK.
|
||||||
|
case nil:
|
||||||
|
// Do nothing, just continue cycle.
|
||||||
|
case io.EOF:
|
||||||
|
break L
|
||||||
|
// Indicate that something went wrong.
|
||||||
|
default:
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// THINK: Maybe, use async running.
|
||||||
|
for _, fi := range fis {
|
||||||
|
// NB: Should work on every Windows and non-Windows OS.
|
||||||
|
if isRegular(fi.Mode()) {
|
||||||
|
if pathIsName {
|
||||||
|
fp = fi.Name()
|
||||||
|
} else {
|
||||||
|
// Build full path of a file.
|
||||||
|
fp = filepath.Join(absDirPath, fi.Name())
|
||||||
|
}
|
||||||
|
// Check filter condition.
|
||||||
|
if fpFilter != nil && !fpFilter(fp) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filePaths = append(filePaths, fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filePaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpenFilesByDirectoryAsync runs async reading directories 'dirPaths' and inserts pairs
|
||||||
|
// in map 'filesInDirMap': Key - directory name, value - *os.File slice.
|
||||||
|
func getOpenFilesByDirectoryAsync(
|
||||||
|
dirPaths []string,
|
||||||
|
fFilter fileFilter,
|
||||||
|
filesInDirMap map[string][]*os.File,
|
||||||
|
) error {
|
||||||
|
n := len(dirPaths)
|
||||||
|
if n > maxDirNumberReadAsync {
|
||||||
|
return fmt.Errorf("number of input directories to be read exceeded max value %d", maxDirNumberReadAsync)
|
||||||
|
}
|
||||||
|
type filesInDirResult struct {
|
||||||
|
DirName string
|
||||||
|
Files []*os.File
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
dirFilesChan := make(chan *filesInDirResult, n)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
// Register n goroutines which are going to do work.
|
||||||
|
wg.Add(n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
// Launch asynchronously the piece of work.
|
||||||
|
go func(dirPath string) {
|
||||||
|
fs, e := getOpenFilesInDir(dirPath, fFilter)
|
||||||
|
dirFilesChan <- &filesInDirResult{filepath.Base(dirPath), fs, e}
|
||||||
|
// Mark the current goroutine as finished (work is done).
|
||||||
|
wg.Done()
|
||||||
|
}(dirPaths[i])
|
||||||
|
}
|
||||||
|
// Wait for all goroutines to finish their work.
|
||||||
|
wg.Wait()
|
||||||
|
// Close the error channel to let for-range clause
|
||||||
|
// get all the buffered values without blocking and quit in the end.
|
||||||
|
close(dirFilesChan)
|
||||||
|
for fidr := range dirFilesChan {
|
||||||
|
if fidr.Error == nil {
|
||||||
|
// THINK: What will happen if the key is already present?
|
||||||
|
filesInDirMap[fidr.DirName] = fidr.Files
|
||||||
|
} else {
|
||||||
|
return fidr.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileExists return flag whether a given file exists
|
||||||
|
// and operation error if an unclassified failure occurs.
|
||||||
|
func fileExists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDirectory makes directory with a given name
|
||||||
|
// making all parent directories if necessary.
|
||||||
|
func createDirectory(dirPath string) error {
|
||||||
|
var dPath string
|
||||||
|
var err error
|
||||||
|
if !filepath.IsAbs(dirPath) {
|
||||||
|
dPath, err = filepath.Abs(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dPath = dirPath
|
||||||
|
}
|
||||||
|
exists, err := fileExists(dPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.MkdirAll(dPath, os.ModeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryRemoveFile gives a try removing the file
|
||||||
|
// only ignoring an error when the file does not exist.
|
||||||
|
func tryRemoveFile(filePath string) (err error) {
|
||||||
|
err = os.Remove(filePath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlNode struct {
|
||||||
|
name string
|
||||||
|
attributes map[string]string
|
||||||
|
children []*xmlNode
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode() *xmlNode {
|
||||||
|
node := new(xmlNode)
|
||||||
|
node.children = make([]*xmlNode, 0)
|
||||||
|
node.attributes = make(map[string]string)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) String() string {
|
||||||
|
str := fmt.Sprintf("<%s", node.name)
|
||||||
|
|
||||||
|
for attrName, attrVal := range node.attributes {
|
||||||
|
str += fmt.Sprintf(" %s=\"%s\"", attrName, attrVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
str += ">"
|
||||||
|
str += node.value
|
||||||
|
|
||||||
|
if len(node.children) != 0 {
|
||||||
|
for _, child := range node.children {
|
||||||
|
str += fmt.Sprintf("%s", child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str += fmt.Sprintf("</%s>", node.name)
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) unmarshal(startEl xml.StartElement) error {
|
||||||
|
node.name = startEl.Name.Local
|
||||||
|
|
||||||
|
for _, v := range startEl.Attr {
|
||||||
|
_, alreadyExists := node.attributes[v.Name.Local]
|
||||||
|
if alreadyExists {
|
||||||
|
return errors.New("tag '" + node.name + "' has duplicated attribute: '" + v.Name.Local + "'")
|
||||||
|
}
|
||||||
|
node.attributes[v.Name.Local] = v.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) add(child *xmlNode) {
|
||||||
|
if node.children == nil {
|
||||||
|
node.children = make([]*xmlNode, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children = append(node.children, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) hasChildren() bool {
|
||||||
|
return node.children != nil && len(node.children) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================
|
||||||
|
|
||||||
|
func unmarshalConfig(reader io.Reader) (*xmlNode, error) {
|
||||||
|
xmlParser := xml.NewDecoder(reader)
|
||||||
|
|
||||||
|
config, err := unmarshalNode(xmlParser, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("xml has no content")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextConfigEntry, err := unmarshalNode(xmlParser, nil)
|
||||||
|
if nextConfigEntry != nil {
|
||||||
|
return nil, errors.New("xml contains more than one root element")
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalNode(xmlParser *xml.Decoder, curToken xml.Token) (node *xmlNode, err error) {
|
||||||
|
firstLoop := true
|
||||||
|
for {
|
||||||
|
var tok xml.Token
|
||||||
|
if firstLoop && curToken != nil {
|
||||||
|
tok = curToken
|
||||||
|
firstLoop = false
|
||||||
|
} else {
|
||||||
|
tok, err = getNextToken(xmlParser)
|
||||||
|
if err != nil || tok == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := tok.(type) {
|
||||||
|
case xml.SyntaxError:
|
||||||
|
err = errors.New(tt.Error())
|
||||||
|
return
|
||||||
|
case xml.CharData:
|
||||||
|
value := strings.TrimSpace(string([]byte(tt)))
|
||||||
|
if node != nil {
|
||||||
|
node.value += value
|
||||||
|
}
|
||||||
|
case xml.StartElement:
|
||||||
|
if node == nil {
|
||||||
|
node = newNode()
|
||||||
|
err := node.unmarshal(tt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
childNode, childErr := unmarshalNode(xmlParser, tok)
|
||||||
|
if childErr != nil {
|
||||||
|
return nil, childErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if childNode != nil {
|
||||||
|
node.add(childNode)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextToken(xmlParser *xml.Decoder) (tok xml.Token, err error) {
|
||||||
|
if tok, err = xmlParser.Token(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,307 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
staticFuncCallDepth = 3 // See 'commonLogger.log' method comments
|
||||||
|
loggerFuncCallDepth = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current is the logger used in all package level convenience funcs like 'Trace', 'Debug', 'Flush', etc.
|
||||||
|
var Current LoggerInterface
|
||||||
|
|
||||||
|
// Default logger that is created from an empty config: "<seelog/>". It is not closed by a ReplaceLogger call.
|
||||||
|
var Default LoggerInterface
|
||||||
|
|
||||||
|
// Disabled logger that doesn't produce any output in any circumstances. It is neither closed nor flushed by a ReplaceLogger call.
|
||||||
|
var Disabled LoggerInterface
|
||||||
|
|
||||||
|
var pkgOperationsMutex *sync.Mutex
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pkgOperationsMutex = new(sync.Mutex)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if Default == nil {
|
||||||
|
Default, err = LoggerFromConfigAsBytes([]byte("<seelog />"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if Disabled == nil {
|
||||||
|
Disabled, err = LoggerFromConfigAsBytes([]byte("<seelog levels=\"off\"/>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Seelog couldn't start. Error: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = Default
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLoggerFromFullConfig(config *configForParsing) (LoggerInterface, error) {
|
||||||
|
if config.LogType == syncloggerTypeFromString {
|
||||||
|
return NewSyncLogger(&config.logConfig), nil
|
||||||
|
} else if config.LogType == asyncLooploggerTypeFromString {
|
||||||
|
return NewAsyncLoopLogger(&config.logConfig), nil
|
||||||
|
} else if config.LogType == asyncTimerloggerTypeFromString {
|
||||||
|
logData := config.LoggerData
|
||||||
|
if logData == nil {
|
||||||
|
return nil, errors.New("async timer data not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncInt, ok := logData.(asyncTimerLoggerData)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid async timer data")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := NewAsyncTimerLogger(&config.logConfig, time.Duration(asyncInt.AsyncInterval))
|
||||||
|
if !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger, nil
|
||||||
|
} else if config.LogType == adaptiveLoggerTypeFromString {
|
||||||
|
logData := config.LoggerData
|
||||||
|
if logData == nil {
|
||||||
|
return nil, errors.New("adaptive logger parameters not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptData, ok := logData.(adaptiveLoggerData)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid adaptive logger parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := NewAsyncAdaptiveLogger(
|
||||||
|
&config.logConfig,
|
||||||
|
time.Duration(adaptData.MinInterval),
|
||||||
|
time.Duration(adaptData.MaxInterval),
|
||||||
|
adaptData.CriticalMsgCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid config log type/data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger sets the 'Current' package level logger variable to the specified value.
|
||||||
|
// This variable is used in all Trace/Debug/... package level convenience funcs.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// after calling
|
||||||
|
// seelog.UseLogger(somelogger)
|
||||||
|
// the following:
|
||||||
|
// seelog.Debug("abc")
|
||||||
|
// will be equal to
|
||||||
|
// somelogger.Debug("abc")
|
||||||
|
//
|
||||||
|
// IMPORTANT: UseLogger do NOT close the previous logger (only flushes it). So if
|
||||||
|
// you constantly use it to replace loggers and don't close them in other code, you'll
|
||||||
|
// end up having memory leaks.
|
||||||
|
//
|
||||||
|
// To safely replace loggers, use ReplaceLogger.
|
||||||
|
func UseLogger(logger LoggerInterface) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
|
||||||
|
oldLogger := Current
|
||||||
|
Current = logger
|
||||||
|
|
||||||
|
if oldLogger != nil {
|
||||||
|
oldLogger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceLogger acts as UseLogger but the logger that was previously
|
||||||
|
// used is disposed (except Default and Disabled loggers).
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// import log "github.com/cihub/seelog"
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.ReplaceLogger(logger)
|
||||||
|
// defer log.Flush()
|
||||||
|
//
|
||||||
|
// log.Trace("test")
|
||||||
|
// log.Debugf("var = %s", "abc")
|
||||||
|
// }
|
||||||
|
func ReplaceLogger(logger LoggerInterface) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("recovered from panic during ReplaceLogger: %s", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if Current == Default {
|
||||||
|
Current.Flush()
|
||||||
|
} else if Current != nil && !Current.Closed() && Current != Disabled {
|
||||||
|
Current.Flush()
|
||||||
|
Current.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = logger
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracef formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Trace.
|
||||||
|
func Tracef(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.traceWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Debug.
|
||||||
|
func Debugf(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.debugWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Info.
|
||||||
|
func Infof(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.infoWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf formats message according to format specifier and writes to default logger with log level = Warn
|
||||||
|
func Warnf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf formats message according to format specifier and writes to default logger with log level = Error
|
||||||
|
func Errorf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criticalf formats message according to format specifier and writes to default logger with log level = Critical
|
||||||
|
func Criticalf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace formats message using the default formats for its operands and writes to default logger with log level = Trace
|
||||||
|
func Trace(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.traceWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug formats message using the default formats for its operands and writes to default logger with log level = Debug
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.debugWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info formats message using the default formats for its operands and writes to default logger with log level = Info
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.infoWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn formats message using the default formats for its operands and writes to default logger with log level = Warn
|
||||||
|
func Warn(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error formats message using the default formats for its operands and writes to default logger with log level = Error
|
||||||
|
func Error(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical formats message using the default formats for its operands and writes to default logger with log level = Critical
|
||||||
|
func Critical(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush immediately processes all currently queued messages and all currently buffered messages.
|
||||||
|
// It is a blocking call which returns only after the queue is empty and all the buffers are empty.
|
||||||
|
//
|
||||||
|
// If Flush is called for a synchronous logger (type='sync'), it only flushes buffers (e.g. '<buffered>' receivers)
|
||||||
|
// , because there is no queue.
|
||||||
|
//
|
||||||
|
// Call this method when your app is going to shut down not to lose any log messages.
|
||||||
|
func Flush() {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.Flush()
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func reportInternalError(err error) {
|
||||||
|
fmt.Fprintf(os.Stderr, "seelog internal error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerInterface represents structs capable of logging Seelog messages
|
||||||
|
type LoggerInterface interface {
|
||||||
|
|
||||||
|
// Tracef formats message according to format specifier
|
||||||
|
// and writes to log with level = Trace.
|
||||||
|
Tracef(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Debugf formats message according to format specifier
|
||||||
|
// and writes to log with level = Debug.
|
||||||
|
Debugf(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Infof formats message according to format specifier
|
||||||
|
// and writes to log with level = Info.
|
||||||
|
Infof(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Warnf formats message according to format specifier
|
||||||
|
// and writes to log with level = Warn.
|
||||||
|
Warnf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Errorf formats message according to format specifier
|
||||||
|
// and writes to log with level = Error.
|
||||||
|
Errorf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Criticalf formats message according to format specifier
|
||||||
|
// and writes to log with level = Critical.
|
||||||
|
Criticalf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Trace formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Trace
|
||||||
|
Trace(v ...interface{})
|
||||||
|
|
||||||
|
// Debug formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Debug
|
||||||
|
Debug(v ...interface{})
|
||||||
|
|
||||||
|
// Info formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Info
|
||||||
|
Info(v ...interface{})
|
||||||
|
|
||||||
|
// Warn formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Warn
|
||||||
|
Warn(v ...interface{}) error
|
||||||
|
|
||||||
|
// Error formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Error
|
||||||
|
Error(v ...interface{}) error
|
||||||
|
|
||||||
|
// Critical formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Critical
|
||||||
|
Critical(v ...interface{}) error
|
||||||
|
|
||||||
|
traceWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
debugWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
infoWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
warnWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
errorWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
criticalWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
|
||||||
|
// Close flushes all the messages in the logger and closes it. It cannot be used after this operation.
|
||||||
|
Close()
|
||||||
|
|
||||||
|
// Flush flushes all the messages in the logger.
|
||||||
|
Flush()
|
||||||
|
|
||||||
|
// Closed returns true if the logger was previously closed.
|
||||||
|
Closed() bool
|
||||||
|
|
||||||
|
// SetAdditionalStackDepth sets the additional number of frames to skip by runtime.Caller
|
||||||
|
// when getting function information needed to print seelog format identifiers such as %Func or %File.
|
||||||
|
//
|
||||||
|
// This func may be used when you wrap seelog funcs and want to print caller info of you own
|
||||||
|
// wrappers instead of seelog func callers. In this case you should set depth = 1. If you then
|
||||||
|
// wrap your wrapper, you should set depth = 2, etc.
|
||||||
|
//
|
||||||
|
// NOTE: Incorrect depth value may lead to errors in runtime.Caller evaluation or incorrect
|
||||||
|
// function/file names in log files. Do not use it if you are not going to wrap seelog funcs.
|
||||||
|
// You may reset the value to default using a SetAdditionalStackDepth(0) call.
|
||||||
|
SetAdditionalStackDepth(depth int) error
|
||||||
|
|
||||||
|
// Sets logger context that can be used in formatter funcs and custom receivers
|
||||||
|
SetContext(context interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// innerLoggerInterface is an internal logging interface
|
||||||
|
type innerLoggerInterface interface {
|
||||||
|
innerLog(level LogLevel, context LogContextInterface, message fmt.Stringer)
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// [file path][func name][level] -> [allowed]
|
||||||
|
type allowedContextCache map[string]map[string]map[LogLevel]bool
|
||||||
|
|
||||||
|
// commonLogger contains all common data needed for logging and contains methods used to log messages.
|
||||||
|
type commonLogger struct {
|
||||||
|
config *logConfig // Config used for logging
|
||||||
|
contextCache allowedContextCache // Caches whether log is enabled for specific "full path-func name-level" sets
|
||||||
|
closed bool // 'true' when all writers are closed, all data is flushed, logger is unusable. Must be accessed while holding closedM
|
||||||
|
closedM sync.RWMutex
|
||||||
|
m sync.Mutex // Mutex for main operations
|
||||||
|
unusedLevels []bool
|
||||||
|
innerLogger innerLoggerInterface
|
||||||
|
addStackDepth int // Additional stack depth needed for correct seelog caller context detection
|
||||||
|
customContext interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonLogger(config *logConfig, internalLogger innerLoggerInterface) *commonLogger {
|
||||||
|
cLogger := new(commonLogger)
|
||||||
|
|
||||||
|
cLogger.config = config
|
||||||
|
cLogger.contextCache = make(allowedContextCache)
|
||||||
|
cLogger.unusedLevels = make([]bool, Off)
|
||||||
|
cLogger.fillUnusedLevels()
|
||||||
|
cLogger.innerLogger = internalLogger
|
||||||
|
|
||||||
|
return cLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) SetAdditionalStackDepth(depth int) error {
|
||||||
|
if depth < 0 {
|
||||||
|
return fmt.Errorf("negative depth: %d", depth)
|
||||||
|
}
|
||||||
|
cLogger.m.Lock()
|
||||||
|
cLogger.addStackDepth = depth
|
||||||
|
cLogger.m.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Tracef(format string, params ...interface{}) {
|
||||||
|
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Debugf(format string, params ...interface{}) {
|
||||||
|
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Infof(format string, params ...interface{}) {
|
||||||
|
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Warnf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Errorf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Criticalf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Trace(v ...interface{}) {
|
||||||
|
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Debug(v ...interface{}) {
|
||||||
|
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Info(v ...interface{}) {
|
||||||
|
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Warn(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Error(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Critical(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) SetContext(c interface{}) {
|
||||||
|
cLogger.customContext = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) traceWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(TraceLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) debugWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(DebugLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) infoWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(InfoLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) warnWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(WarnLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) errorWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(ErrorLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) criticalWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(CriticalLvl, message, callDepth)
|
||||||
|
cLogger.innerLogger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Closed() bool {
|
||||||
|
cLogger.closedM.RLock()
|
||||||
|
defer cLogger.closedM.RUnlock()
|
||||||
|
return cLogger.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) fillUnusedLevels() {
|
||||||
|
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||||
|
cLogger.unusedLevels[i] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
cLogger.fillUnusedLevelsByContraint(cLogger.config.Constraints)
|
||||||
|
|
||||||
|
for _, exception := range cLogger.config.Exceptions {
|
||||||
|
cLogger.fillUnusedLevelsByContraint(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) fillUnusedLevelsByContraint(constraint logLevelConstraints) {
|
||||||
|
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||||
|
if constraint.IsAllowed(LogLevel(i)) {
|
||||||
|
cLogger.unusedLevels[i] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stackCallDepth is used to indicate the call depth of 'log' func.
|
||||||
|
// This depth level is used in the runtime.Caller(...) call. See
|
||||||
|
// common_context.go -> specifyContext, extractCallerInfo for details.
|
||||||
|
func (cLogger *commonLogger) log(level LogLevel, message fmt.Stringer, stackCallDepth int) {
|
||||||
|
if cLogger.unusedLevels[level] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cLogger.m.Lock()
|
||||||
|
defer cLogger.m.Unlock()
|
||||||
|
|
||||||
|
if cLogger.Closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context, _ := specifyContext(stackCallDepth+cLogger.addStackDepth, cLogger.customContext)
|
||||||
|
// Context errors are not reported because there are situations
|
||||||
|
// in which context errors are normal Seelog usage cases. For
|
||||||
|
// example in executables with stripped symbols.
|
||||||
|
// Error contexts are returned instead. See common_context.go.
|
||||||
|
/*if err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
return
|
||||||
|
}*/
|
||||||
|
cLogger.innerLogger.innerLog(level, context, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) processLogMsg(level LogLevel, message fmt.Stringer, context LogContextInterface) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("recovered from panic during message processing: %s", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if cLogger.config.IsAllowed(level, context) {
|
||||||
|
cLogger.config.RootDispatcher.Dispatch(message.String(), level, context, reportInternalError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) isAllowed(level LogLevel, context LogContextInterface) bool {
|
||||||
|
funcMap, ok := cLogger.contextCache[context.FullPath()]
|
||||||
|
if !ok {
|
||||||
|
funcMap = make(map[string]map[LogLevel]bool, 0)
|
||||||
|
cLogger.contextCache[context.FullPath()] = funcMap
|
||||||
|
}
|
||||||
|
|
||||||
|
levelMap, ok := funcMap[context.Func()]
|
||||||
|
if !ok {
|
||||||
|
levelMap = make(map[LogLevel]bool, 0)
|
||||||
|
funcMap[context.Func()] = levelMap
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllowValue, ok := levelMap[level]
|
||||||
|
if !ok {
|
||||||
|
isAllowValue = cLogger.config.IsAllowed(level, context)
|
||||||
|
levelMap[level] = isAllowValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAllowValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type logMessage struct {
|
||||||
|
params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logFormattedMessage struct {
|
||||||
|
format string
|
||||||
|
params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogMessage(params []interface{}) fmt.Stringer {
|
||||||
|
message := new(logMessage)
|
||||||
|
|
||||||
|
message.params = params
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogFormattedMessage(format string, params []interface{}) *logFormattedMessage {
|
||||||
|
message := new(logFormattedMessage)
|
||||||
|
|
||||||
|
message.params = params
|
||||||
|
message.format = format
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (message *logMessage) String() string {
|
||||||
|
return fmt.Sprint(message.params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (message *logFormattedMessage) String() string {
|
||||||
|
return fmt.Sprintf(message.format, message.params...)
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bufferedWriter stores data in memory and flushes it every flushPeriod or when buffer is full
|
||||||
|
type bufferedWriter struct {
|
||||||
|
flushPeriod time.Duration // data flushes interval (in microseconds)
|
||||||
|
bufferMutex *sync.Mutex // mutex for buffer operations syncronization
|
||||||
|
innerWriter io.Writer // inner writer
|
||||||
|
buffer *bufio.Writer // buffered wrapper for inner writer
|
||||||
|
bufferSize int // max size of data chunk in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedWriter creates a new buffered writer struct.
|
||||||
|
// bufferSize -- size of memory buffer in bytes
|
||||||
|
// flushPeriod -- period in which data flushes from memory buffer in milliseconds. 0 - turn off this functionality
|
||||||
|
func NewBufferedWriter(innerWriter io.Writer, bufferSize int, flushPeriod time.Duration) (*bufferedWriter, error) {
|
||||||
|
|
||||||
|
if innerWriter == nil {
|
||||||
|
return nil, errors.New("argument is nil: innerWriter")
|
||||||
|
}
|
||||||
|
if flushPeriod < 0 {
|
||||||
|
return nil, fmt.Errorf("flushPeriod can not be less than 0. Got: %d", flushPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bufferSize <= 0 {
|
||||||
|
return nil, fmt.Errorf("bufferSize can not be less or equal to 0. Got: %d", bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bufio.NewWriterSize(innerWriter, bufferSize)
|
||||||
|
|
||||||
|
/*if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}*/
|
||||||
|
|
||||||
|
newWriter := new(bufferedWriter)
|
||||||
|
|
||||||
|
newWriter.innerWriter = innerWriter
|
||||||
|
newWriter.buffer = buffer
|
||||||
|
newWriter.bufferSize = bufferSize
|
||||||
|
newWriter.flushPeriod = flushPeriod * 1e6
|
||||||
|
newWriter.bufferMutex = new(sync.Mutex)
|
||||||
|
|
||||||
|
if flushPeriod != 0 {
|
||||||
|
go newWriter.flushPeriodically()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) writeBigChunk(bytes []byte) (n int, err error) {
|
||||||
|
bufferedLen := bufWriter.buffer.Buffered()
|
||||||
|
|
||||||
|
n, err = bufWriter.flushInner()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
written, writeErr := bufWriter.innerWriter.Write(bytes)
|
||||||
|
return bufferedLen + written, writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends data to buffer manager. Waits until all buffers are full.
|
||||||
|
func (bufWriter *bufferedWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bytesLen := len(bytes)
|
||||||
|
|
||||||
|
if bytesLen > bufWriter.bufferSize {
|
||||||
|
return bufWriter.writeBigChunk(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytesLen > bufWriter.buffer.Available() {
|
||||||
|
n, err = bufWriter.flushInner()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bufWriter.buffer.Write(bytes)
|
||||||
|
|
||||||
|
return len(bytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) Close() error {
|
||||||
|
closer, ok := bufWriter.innerWriter.(io.Closer)
|
||||||
|
if ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) Flush() {
|
||||||
|
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bufWriter.flushInner()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushInner() (n int, err error) {
|
||||||
|
bufferedLen := bufWriter.buffer.Buffered()
|
||||||
|
flushErr := bufWriter.buffer.Flush()
|
||||||
|
|
||||||
|
return bufWriter.buffer.Buffered() - bufferedLen, flushErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushBuffer() {
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bufWriter.buffer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushPeriodically() {
|
||||||
|
if bufWriter.flushPeriod > 0 {
|
||||||
|
ticker := time.NewTicker(bufWriter.flushPeriod)
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
bufWriter.flushBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) String() string {
|
||||||
|
return fmt.Sprintf("bufferedWriter size: %d, flushPeriod: %d", bufWriter.bufferSize, bufWriter.flushPeriod)
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connWriter is used to write to a stream-oriented network connection.
|
||||||
|
type connWriter struct {
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
reconnectOnMsg bool
|
||||||
|
reconnect bool
|
||||||
|
net string
|
||||||
|
addr string
|
||||||
|
useTLS bool
|
||||||
|
configTLS *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates writer to the address addr on the network netName.
|
||||||
|
// Connection will be opened on each write if reconnectOnMsg = true
|
||||||
|
func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter {
|
||||||
|
newWriter := new(connWriter)
|
||||||
|
|
||||||
|
newWriter.net = netName
|
||||||
|
newWriter.addr = addr
|
||||||
|
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||||
|
|
||||||
|
return newWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a writer that uses SSL/TLS
|
||||||
|
func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter {
|
||||||
|
newWriter := new(connWriter)
|
||||||
|
|
||||||
|
newWriter.net = netName
|
||||||
|
newWriter.addr = addr
|
||||||
|
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||||
|
newWriter.useTLS = true
|
||||||
|
newWriter.configTLS = config
|
||||||
|
|
||||||
|
return newWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) Close() error {
|
||||||
|
if connWriter.innerWriter == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return connWriter.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
if connWriter.neededConnectOnMsg() {
|
||||||
|
err = connWriter.connect()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.reconnectOnMsg {
|
||||||
|
defer connWriter.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = connWriter.innerWriter.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
connWriter.reconnect = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) String() string {
|
||||||
|
return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) connect() error {
|
||||||
|
if connWriter.innerWriter != nil {
|
||||||
|
connWriter.innerWriter.Close()
|
||||||
|
connWriter.innerWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.useTLS {
|
||||||
|
conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connWriter.innerWriter = conn
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial(connWriter.net, connWriter.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn, ok := conn.(*net.TCPConn)
|
||||||
|
if ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
connWriter.innerWriter = conn
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) neededConnectOnMsg() bool {
|
||||||
|
if connWriter.reconnect {
|
||||||
|
connWriter.reconnect = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.innerWriter == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return connWriter.reconnectOnMsg
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// consoleWriter is used to write to console
|
||||||
|
type consoleWriter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new console writer. Returns error, if the console writer couldn't be created.
|
||||||
|
func NewConsoleWriter() (writer *consoleWriter, err error) {
|
||||||
|
newWriter := new(consoleWriter)
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create folder and file on WriteLog/Write first call
|
||||||
|
func (console *consoleWriter) Write(bytes []byte) (int, error) {
|
||||||
|
return fmt.Print(string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (console *consoleWriter) String() string {
|
||||||
|
return "Console writer"
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileWriter is used to write to a file.
|
||||||
|
type fileWriter struct {
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
fileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new file and a corresponding writer. Returns error, if the file couldn't be created.
|
||||||
|
func NewFileWriter(fileName string) (writer *fileWriter, err error) {
|
||||||
|
newWriter := new(fileWriter)
|
||||||
|
newWriter.fileName = fileName
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) Close() error {
|
||||||
|
if fw.innerWriter != nil {
|
||||||
|
err := fw.innerWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fw.innerWriter = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create folder and file on WriteLog/Write first call
|
||||||
|
func (fw *fileWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
if fw.innerWriter == nil {
|
||||||
|
if err := fw.createFile(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fw.innerWriter.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) createFile() error {
|
||||||
|
folder, _ := filepath.Split(fw.fileName)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if 0 != len(folder) {
|
||||||
|
err = os.MkdirAll(folder, defaultDirectoryPermissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exists
|
||||||
|
fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) String() string {
|
||||||
|
return fmt.Sprintf("File writer: %s", fw.fileName)
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type formattedWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
formatter *formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &formattedWriter{writer, formatter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error {
|
||||||
|
str := formattedWriter.formatter.Format(message, level, context)
|
||||||
|
_, err := formattedWriter.writer.Write([]byte(str))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) String() string {
|
||||||
|
return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Writer() io.Writer {
|
||||||
|
return formattedWriter.writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Format() *formatter {
|
||||||
|
return formattedWriter.formatter
|
||||||
|
}
|
|
@ -0,0 +1,782 @@
|
||||||
|
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive"
|
||||||
|
"github.com/cihub/seelog/archive/gzip"
|
||||||
|
"github.com/cihub/seelog/archive/tar"
|
||||||
|
"github.com/cihub/seelog/archive/zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common constants
|
||||||
|
const (
|
||||||
|
rollingLogHistoryDelimiter = "."
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of the rolling writer: roll by date, by time, etc.
|
||||||
|
type rollingType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingTypeSize = iota
|
||||||
|
rollingTypeTime
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of the rolled file naming mode: prefix, postfix, etc.
|
||||||
|
type rollingNameMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingNameModePostfix = iota
|
||||||
|
rollingNameModePrefix
|
||||||
|
)
|
||||||
|
|
||||||
|
var rollingNameModesStringRepresentation = map[rollingNameMode]string{
|
||||||
|
rollingNameModePostfix: "postfix",
|
||||||
|
rollingNameModePrefix: "prefix",
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) {
|
||||||
|
for tp, tpStr := range rollingNameModesStringRepresentation {
|
||||||
|
if tpStr == rollingNameStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var rollingTypesStringRepresentation = map[rollingType]string{
|
||||||
|
rollingTypeSize: "size",
|
||||||
|
rollingTypeTime: "date",
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) {
|
||||||
|
for tp, tpStr := range rollingTypesStringRepresentation {
|
||||||
|
if tpStr == rollingTypeStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old logs archivation type.
|
||||||
|
type rollingArchiveType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingArchiveNone = iota
|
||||||
|
rollingArchiveZip
|
||||||
|
rollingArchiveGzip
|
||||||
|
)
|
||||||
|
|
||||||
|
var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{
|
||||||
|
rollingArchiveNone: "none",
|
||||||
|
rollingArchiveZip: "zip",
|
||||||
|
rollingArchiveGzip: "gzip",
|
||||||
|
}
|
||||||
|
|
||||||
|
type archiver func(f *os.File, exploded bool) archive.WriteCloser
|
||||||
|
|
||||||
|
type unarchiver func(f *os.File) (archive.ReadCloser, error)
|
||||||
|
|
||||||
|
type compressionType struct {
|
||||||
|
extension string
|
||||||
|
handleMultipleEntries bool
|
||||||
|
archiver archiver
|
||||||
|
unarchiver unarchiver
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressionTypes = map[rollingArchiveType]compressionType{
|
||||||
|
rollingArchiveZip: {
|
||||||
|
extension: ".zip",
|
||||||
|
handleMultipleEntries: true,
|
||||||
|
archiver: func(f *os.File, _ bool) archive.WriteCloser {
|
||||||
|
return zip.NewWriter(f)
|
||||||
|
},
|
||||||
|
unarchiver: func(f *os.File) (archive.ReadCloser, error) {
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err := zip.NewReader(f, fi.Size())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return archive.NopCloser(r), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollingArchiveGzip: {
|
||||||
|
extension: ".gz",
|
||||||
|
handleMultipleEntries: false,
|
||||||
|
archiver: func(f *os.File, exploded bool) archive.WriteCloser {
|
||||||
|
gw := gzip.NewWriter(f)
|
||||||
|
if exploded {
|
||||||
|
return gw
|
||||||
|
}
|
||||||
|
return tar.NewWriteMultiCloser(gw, gw)
|
||||||
|
},
|
||||||
|
unarchiver: func(f *os.File) (archive.ReadCloser, error) {
|
||||||
|
gr, err := gzip.NewReader(f, f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if the gzip is a tar
|
||||||
|
tr := tar.NewReader(gr)
|
||||||
|
_, err = tr.Next()
|
||||||
|
isTar := err == nil
|
||||||
|
|
||||||
|
// Reset to beginning of file
|
||||||
|
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gr.Reset(f)
|
||||||
|
|
||||||
|
if isTar {
|
||||||
|
return archive.NopCloser(tar.NewReader(gr)), nil
|
||||||
|
}
|
||||||
|
return gr, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (compressionType *compressionType) rollingArchiveTypeName(name string, exploded bool) string {
|
||||||
|
if !compressionType.handleMultipleEntries && !exploded {
|
||||||
|
return name + ".tar" + compressionType.extension
|
||||||
|
} else {
|
||||||
|
return name + compressionType.extension
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) {
|
||||||
|
for tp, tpStr := range rollingArchiveTypesStringRepresentation {
|
||||||
|
if tpStr == rollingArchiveTypeStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default names for different archive types
|
||||||
|
var rollingArchiveDefaultExplodedName = "old"
|
||||||
|
|
||||||
|
func rollingArchiveTypeDefaultName(archiveType rollingArchiveType, exploded bool) (string, error) {
|
||||||
|
compressionType, ok := compressionTypes[archiveType]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("cannot get default filename for archive type = %v", archiveType)
|
||||||
|
}
|
||||||
|
return compressionType.rollingArchiveTypeName("log", exploded), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rollInfo struct {
|
||||||
|
Name string
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollerVirtual is an interface that represents all virtual funcs that are
|
||||||
|
// called in different rolling writer subtypes.
|
||||||
|
type rollerVirtual interface {
|
||||||
|
needsToRoll(lastRollTime time.Time) (bool, error) // Returns true if needs to switch to another file.
|
||||||
|
isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok.
|
||||||
|
sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger.
|
||||||
|
|
||||||
|
// Creates a new froll history file using the contents of current file and special filename of the latest roll (prefix/ postfix).
|
||||||
|
// If lastRollName is empty (""), then it means that there is no latest roll (current is the first one)
|
||||||
|
getNewHistoryRollFileName(lastRoll rollInfo) string
|
||||||
|
|
||||||
|
getCurrentFileName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollingFileWriter writes received messages to a file, until time interval passes
|
||||||
|
// or file exceeds a specified limit. After that the current log file is renamed
|
||||||
|
// and writer starts to log into a new file. You can set a limit for such renamed
|
||||||
|
// files count, if you want, and then the rolling writer would delete older ones when
|
||||||
|
// the files count exceed the specified limit.
|
||||||
|
type rollingFileWriter struct {
|
||||||
|
fileName string // log file name
|
||||||
|
currentDirPath string
|
||||||
|
currentFile *os.File
|
||||||
|
currentName string
|
||||||
|
currentFileSize int64
|
||||||
|
rollingType rollingType // Rolling mode (Files roll by size/date/...)
|
||||||
|
archiveType rollingArchiveType
|
||||||
|
archivePath string
|
||||||
|
archiveExploded bool
|
||||||
|
fullName bool
|
||||||
|
maxRolls int
|
||||||
|
nameMode rollingNameMode
|
||||||
|
self rollerVirtual // Used for virtual calls
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode,
|
||||||
|
archiveExploded bool, fullName bool) (*rollingFileWriter, error) {
|
||||||
|
rw := new(rollingFileWriter)
|
||||||
|
rw.currentDirPath, rw.fileName = filepath.Split(fpath)
|
||||||
|
if len(rw.currentDirPath) == 0 {
|
||||||
|
rw.currentDirPath = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.rollingType = rtype
|
||||||
|
rw.archiveType = atype
|
||||||
|
rw.archivePath = apath
|
||||||
|
rw.nameMode = namemode
|
||||||
|
rw.maxRolls = maxr
|
||||||
|
rw.archiveExploded = archiveExploded
|
||||||
|
rw.fullName = fullName
|
||||||
|
return rw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) hasRollName(file string) bool {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
rname := rw.fileName + rollingLogHistoryDelimiter
|
||||||
|
return strings.HasPrefix(file, rname)
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
rname := rollingLogHistoryDelimiter + rw.fileName
|
||||||
|
return strings.HasSuffix(file, rname)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
return originalName + rollingLogHistoryDelimiter + rollname
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
return rollname + rollingLogHistoryDelimiter + originalName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) {
|
||||||
|
files, err := getDirFilePaths(rw.currentDirPath, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var validRollNames []string
|
||||||
|
for _, file := range files {
|
||||||
|
if rw.hasRollName(file) {
|
||||||
|
rname := rw.getFileRollName(file)
|
||||||
|
if rw.self.isFileRollNameValid(rname) {
|
||||||
|
validRollNames = append(validRollNames, rname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
validSortedFiles := make([]string, len(sortedTails))
|
||||||
|
for i, v := range sortedTails {
|
||||||
|
validSortedFiles[i] = rw.createFullFileName(rw.fileName, v)
|
||||||
|
}
|
||||||
|
return validSortedFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(rw.currentDirPath) != 0 {
|
||||||
|
err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rw.currentName = rw.self.getCurrentFileName()
|
||||||
|
filePath := filepath.Join(rw.currentDirPath, rw.currentName)
|
||||||
|
|
||||||
|
// If exists
|
||||||
|
stat, err := os.Lstat(filePath)
|
||||||
|
if err == nil {
|
||||||
|
rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, defaultFilePermissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err = os.Lstat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.currentFileSize = stat.Size()
|
||||||
|
} else {
|
||||||
|
rw.currentFile, err = os.Create(filePath)
|
||||||
|
rw.currentFileSize = 0
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) archiveExplodedLogs(logFilename string, compressionType compressionType) (err error) {
|
||||||
|
closeWithError := func(c io.Closer) {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rollPath := filepath.Join(rw.currentDirPath, logFilename)
|
||||||
|
src, err := os.Open(rollPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
|
||||||
|
// Buffer to a temporary file on the same partition
|
||||||
|
// Note: archivePath is a path to a directory when handling exploded logs
|
||||||
|
dst, err := rw.tempArchiveFile(rw.archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeWithError(dst)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize archive by swapping the buffered archive into place
|
||||||
|
err = os.Rename(dst.Name(), filepath.Join(rw.archivePath,
|
||||||
|
compressionType.rollingArchiveTypeName(logFilename, true)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// archive entry
|
||||||
|
w := compressionType.archiver(dst, true)
|
||||||
|
defer closeWithError(w)
|
||||||
|
fi, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.NextFile(logFilename, fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) archiveUnexplodedLogs(compressionType compressionType, rollsToDelete int, history []string) (err error) {
|
||||||
|
closeWithError := func(c io.Closer) {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer to a temporary file on the same partition
|
||||||
|
// Note: archivePath is a path to a file when handling unexploded logs
|
||||||
|
dst, err := rw.tempArchiveFile(filepath.Dir(rw.archivePath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeWithError(dst)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize archive by moving the buffered archive into place
|
||||||
|
err = os.Rename(dst.Name(), rw.archivePath)
|
||||||
|
}()
|
||||||
|
|
||||||
|
w := compressionType.archiver(dst, false)
|
||||||
|
defer closeWithError(w)
|
||||||
|
|
||||||
|
src, err := os.Open(rw.archivePath)
|
||||||
|
switch {
|
||||||
|
// Archive exists
|
||||||
|
case err == nil:
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
|
||||||
|
r, err := compressionType.unarchiver(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close() // Read-only
|
||||||
|
|
||||||
|
if err := archive.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to stat
|
||||||
|
case !os.IsNotExist(err):
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new files to the archive
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
rollPath := filepath.Join(rw.currentDirPath, history[i])
|
||||||
|
src, err := os.Open(rollPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
fi, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.NextFile(src.Name(), fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) deleteOldRolls(history []string) error {
|
||||||
|
if rw.maxRolls <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rollsToDelete := len(history) - rw.maxRolls
|
||||||
|
if rollsToDelete <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rw.archiveType != rollingArchiveNone {
|
||||||
|
if rw.archiveExploded {
|
||||||
|
os.MkdirAll(rw.archivePath, defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
// Archive logs
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
rw.archiveExplodedLogs(history[i], compressionTypes[rw.archiveType])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.MkdirAll(filepath.Dir(rw.archivePath), defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
rw.archiveUnexplodedLogs(compressionTypes[rw.archiveType], rollsToDelete, history)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// In all cases (archive files or not) the files should be deleted.
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
// Try best to delete files without breaking the loop.
|
||||||
|
if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) getFileRollName(fileName string) string {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
return fileName[len(rw.fileName+rollingLogHistoryDelimiter):]
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
return fileName[:len(fileName)-len(rw.fileName+rollingLogHistoryDelimiter)]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
if rw.currentFile == nil {
|
||||||
|
err := rw.createFileAndFolderIfNeeded(true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// needs to roll if:
|
||||||
|
// * file roller max file size exceeded OR
|
||||||
|
// * time roller interval passed
|
||||||
|
fi, err := rw.currentFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
lastRollTime := fi.ModTime()
|
||||||
|
nr, err := rw.self.needsToRoll(lastRollTime)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if nr {
|
||||||
|
// First, close current file.
|
||||||
|
err = rw.currentFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Current history of all previous log files.
|
||||||
|
// For file roller it may be like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.4
|
||||||
|
// * file.log.5
|
||||||
|
// * file.log.6
|
||||||
|
//
|
||||||
|
// For date roller it may look like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.11.Aug.13
|
||||||
|
// * file.log.15.Aug.13
|
||||||
|
// * file.log.16.Aug.13
|
||||||
|
// Sorted log history does NOT include current file.
|
||||||
|
history, err := rw.getSortedLogHistory()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Renames current file to create a new roll history entry
|
||||||
|
// For file roller it may be like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.4
|
||||||
|
// * file.log.5
|
||||||
|
// * file.log.6
|
||||||
|
// n file.log.7 <---- RENAMED (from file.log)
|
||||||
|
// Time rollers that doesn't modify file names (e.g. 'date' roller) skip this logic.
|
||||||
|
var newHistoryName string
|
||||||
|
lastRoll := rollInfo{
|
||||||
|
Time: lastRollTime,
|
||||||
|
}
|
||||||
|
if len(history) > 0 {
|
||||||
|
// Create new rname name using last history file name
|
||||||
|
lastRoll.Name = rw.getFileRollName(history[len(history)-1])
|
||||||
|
} else {
|
||||||
|
// Create first rname name
|
||||||
|
lastRoll.Name = ""
|
||||||
|
}
|
||||||
|
newRollMarkerName := rw.self.getNewHistoryRollFileName(lastRoll)
|
||||||
|
if len(newRollMarkerName) != 0 {
|
||||||
|
newHistoryName = rw.createFullFileName(rw.fileName, newRollMarkerName)
|
||||||
|
} else {
|
||||||
|
newHistoryName = rw.fileName
|
||||||
|
}
|
||||||
|
if newHistoryName != rw.fileName {
|
||||||
|
err = os.Rename(filepath.Join(rw.currentDirPath, rw.currentName), filepath.Join(rw.currentDirPath, newHistoryName))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally, add the newly added history file to the history archive
|
||||||
|
// and, if after that the archive exceeds the allowed max limit, older rolls
|
||||||
|
// must the removed/archived.
|
||||||
|
history = append(history, newHistoryName)
|
||||||
|
if len(history) > rw.maxRolls {
|
||||||
|
err = rw.deleteOldRolls(history)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rw.createFileAndFolderIfNeeded(false)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.currentFileSize += int64(len(bytes))
|
||||||
|
return rw.currentFile.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) Close() error {
|
||||||
|
if rw.currentFile != nil {
|
||||||
|
e := rw.currentFile.Close()
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
rw.currentFile = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) tempArchiveFile(archiveDir string) (*os.File, error) {
|
||||||
|
tmp := filepath.Join(archiveDir, ".seelog_tmp")
|
||||||
|
if err := os.MkdirAll(tmp, defaultDirectoryPermissions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.TempFile(tmp, "archived_logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================================
|
||||||
|
// Different types of rolling writers
|
||||||
|
// =============================================================================================
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Rolling writer by SIZE
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// rollingFileWriterSize performs roll when file exceeds a specified limit.
|
||||||
|
type rollingFileWriterSize struct {
|
||||||
|
*rollingFileWriter
|
||||||
|
maxFileSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode, archiveExploded bool) (*rollingFileWriterSize, error) {
|
||||||
|
rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode, archiveExploded, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rws := &rollingFileWriterSize{rw, maxSize}
|
||||||
|
rws.self = rws
|
||||||
|
return rws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) needsToRoll(lastRollTime time.Time) (bool, error) {
|
||||||
|
return rws.currentFileSize >= rws.maxFileSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool {
|
||||||
|
if len(rname) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := strconv.Atoi(rname)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rollSizeFileTailsSlice []string
|
||||||
|
|
||||||
|
func (p rollSizeFileTailsSlice) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
func (p rollSizeFileTailsSlice) Less(i, j int) bool {
|
||||||
|
v1, _ := strconv.Atoi(p[i])
|
||||||
|
v2, _ := strconv.Atoi(p[j])
|
||||||
|
return v1 < v2
|
||||||
|
}
|
||||||
|
func (p rollSizeFileTailsSlice) Swap(i, j int) {
|
||||||
|
p[i], p[j] = p[j], p[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||||
|
ss := rollSizeFileTailsSlice(fs)
|
||||||
|
sort.Sort(ss)
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) getNewHistoryRollFileName(lastRoll rollInfo) string {
|
||||||
|
v := 0
|
||||||
|
if len(lastRoll.Name) != 0 {
|
||||||
|
v, _ = strconv.Atoi(lastRoll.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", v+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) getCurrentFileName() string {
|
||||||
|
return rws.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) String() string {
|
||||||
|
return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v",
|
||||||
|
rws.fileName,
|
||||||
|
rollingArchiveTypesStringRepresentation[rws.archiveType],
|
||||||
|
rws.archivePath,
|
||||||
|
rws.maxFileSize,
|
||||||
|
rws.maxRolls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Rolling writer by TIME
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// rollingFileWriterTime performs roll when a specified time interval has passed.
|
||||||
|
type rollingFileWriterTime struct {
|
||||||
|
*rollingFileWriter
|
||||||
|
timePattern string
|
||||||
|
currentTimeFileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int,
|
||||||
|
timePattern string, namemode rollingNameMode, archiveExploded bool, fullName bool) (*rollingFileWriterTime, error) {
|
||||||
|
|
||||||
|
rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode, archiveExploded, fullName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rws := &rollingFileWriterTime{rw, timePattern, ""}
|
||||||
|
rws.self = rws
|
||||||
|
return rws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) needsToRoll(lastRollTime time.Time) (bool, error) {
|
||||||
|
if time.Now().Format(rwt.timePattern) == lastRollTime.Format(rwt.timePattern) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool {
|
||||||
|
if len(rname) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := time.ParseInLocation(rwt.timePattern, rname, time.Local)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rollTimeFileTailsSlice struct {
|
||||||
|
data []string
|
||||||
|
pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Len() int {
|
||||||
|
return len(p.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Less(i, j int) bool {
|
||||||
|
t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local)
|
||||||
|
t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local)
|
||||||
|
return t1.Before(t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Swap(i, j int) {
|
||||||
|
p.data[i], p.data[j] = p.data[j], p.data[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||||
|
ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern}
|
||||||
|
sort.Sort(ss)
|
||||||
|
return ss.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(lastRoll rollInfo) string {
|
||||||
|
return lastRoll.Time.Format(rwt.timePattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) getCurrentFileName() string {
|
||||||
|
if rwt.fullName {
|
||||||
|
return rwt.createFullFileName(rwt.fileName, time.Now().Format(rwt.timePattern))
|
||||||
|
}
|
||||||
|
return rwt.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) String() string {
|
||||||
|
return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, pattern: %s, maxRolls: %v",
|
||||||
|
rwt.fileName,
|
||||||
|
rollingArchiveTypesStringRepresentation[rwt.archiveType],
|
||||||
|
rwt.archivePath,
|
||||||
|
rwt.timePattern,
|
||||||
|
rwt.maxRolls)
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/smtp"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Default subject phrase for sending emails.
|
||||||
|
DefaultSubjectPhrase = "Diagnostic message from server: "
|
||||||
|
|
||||||
|
// Message subject pattern composed according to RFC 5321.
|
||||||
|
rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smtpWriter is used to send emails via given SMTP-server.
|
||||||
|
type smtpWriter struct {
|
||||||
|
auth smtp.Auth
|
||||||
|
hostName string
|
||||||
|
hostPort string
|
||||||
|
hostNameWithPort string
|
||||||
|
senderAddress string
|
||||||
|
senderName string
|
||||||
|
recipientAddresses []string
|
||||||
|
caCertDirPaths []string
|
||||||
|
mailHeaders []string
|
||||||
|
subject string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPWriter returns a new SMTP-writer.
|
||||||
|
func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter {
|
||||||
|
return &smtpWriter{
|
||||||
|
auth: smtp.PlainAuth("", un, pwd, hn),
|
||||||
|
hostName: hn,
|
||||||
|
hostPort: hp,
|
||||||
|
hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp),
|
||||||
|
senderAddress: sa,
|
||||||
|
senderName: sn,
|
||||||
|
recipientAddresses: ras,
|
||||||
|
caCertDirPaths: cacdps,
|
||||||
|
subject: subj,
|
||||||
|
mailHeaders: headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte {
|
||||||
|
headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject)
|
||||||
|
// Build header lines if configured.
|
||||||
|
if headers != nil && len(headers) > 0 {
|
||||||
|
headerLines += strings.Join(headers, "\n")
|
||||||
|
headerLines += "\n"
|
||||||
|
}
|
||||||
|
return append([]byte(headerLines), body...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTLSConfig gets paths of PEM files with certificates,
|
||||||
|
// host server name and tries to create an appropriate TLS.Config.
|
||||||
|
func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) {
|
||||||
|
if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 {
|
||||||
|
err = errors.New("invalid PEM file paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pemEncodedContent := []byte{}
|
||||||
|
var (
|
||||||
|
e error
|
||||||
|
bytes []byte
|
||||||
|
)
|
||||||
|
// Create a file-filter-by-extension, set aside non-pem files.
|
||||||
|
pemFilePathFilter := func(fp string) bool {
|
||||||
|
if filepath.Ext(fp) == ".pem" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, pemFileDirPath := range pemFileDirPaths {
|
||||||
|
pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put together all the PEM files to decode them as a whole byte slice.
|
||||||
|
for _, pfp := range pemFilePaths {
|
||||||
|
if bytes, e = ioutil.ReadFile(pfp); e == nil {
|
||||||
|
pemEncodedContent = append(pemEncodedContent, bytes...)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName}
|
||||||
|
isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent)
|
||||||
|
if !isAppended {
|
||||||
|
// Extract this into a separate error.
|
||||||
|
err = errors.New("invalid PEM content")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMail accepts TLS configuration, connects to the server at addr,
|
||||||
|
// switches to TLS if possible, authenticates with mechanism a if possible,
|
||||||
|
// and then sends an email from address from, to addresses to, with message msg.
|
||||||
|
func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||||||
|
c, err := smtp.Dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if the server supports STARTTLS extension.
|
||||||
|
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||||
|
if err = c.StartTLS(config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if the server supports AUTH extension and use given smtp.Auth.
|
||||||
|
if a != nil {
|
||||||
|
if isSupported, _ := c.Extension("AUTH"); isSupported {
|
||||||
|
if err = c.Auth(a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Portion of code from the official smtp.SendMail function,
|
||||||
|
// see http://golang.org/src/pkg/net/smtp/smtp.go.
|
||||||
|
if err = c.Mail(from); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, addr := range to {
|
||||||
|
if err = c.Rcpt(addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write pushes a text message properly composed according to RFC 5321
|
||||||
|
// to a post server, which sends it to the recipients.
|
||||||
|
func (smtpw *smtpWriter) Write(data []byte) (int, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if smtpw.caCertDirPaths == nil {
|
||||||
|
err = smtp.SendMail(
|
||||||
|
smtpw.hostNameWithPort,
|
||||||
|
smtpw.auth,
|
||||||
|
smtpw.senderAddress,
|
||||||
|
smtpw.recipientAddresses,
|
||||||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName)
|
||||||
|
if e != nil {
|
||||||
|
return 0, e
|
||||||
|
}
|
||||||
|
err = sendMailWithTLSConfig(
|
||||||
|
config,
|
||||||
|
smtpw.hostNameWithPort,
|
||||||
|
smtpw.auth,
|
||||||
|
smtpw.senderAddress,
|
||||||
|
smtpw.recipientAddresses,
|
||||||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes down SMTP-connection.
|
||||||
|
func (smtpw *smtpWriter) Close() error {
|
||||||
|
// Do nothing as Write method opens and closes connection automatically.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Jonathan Leibiusky and Marcos Lilljedahl
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
||||||
|
test:
|
||||||
|
go get -v -d -t ./...
|
||||||
|
go test -v
|
|
@ -0,0 +1,444 @@
|
||||||
|
[![Build Status](https://img.shields.io/travis/franela/goreq/master.svg)](https://travis-ci.org/franela/goreq)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/franela/goreq?status.svg)](https://godoc.org/github.com/franela/goreq)
|
||||||
|
|
||||||
|
GoReq
|
||||||
|
=======
|
||||||
|
|
||||||
|
Simple and sane HTTP request library for Go language.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Table of Contents**
|
||||||
|
|
||||||
|
- [Why GoReq?](#user-content-why-goreq)
|
||||||
|
- [How do I install it?](#user-content-how-do-i-install-it)
|
||||||
|
- [What can I do with it?](#user-content-what-can-i-do-with-it)
|
||||||
|
- [Making requests with different methods](#user-content-making-requests-with-different-methods)
|
||||||
|
- [GET](#user-content-get)
|
||||||
|
- [Tags](#user-content-tags)
|
||||||
|
- [POST](#user-content-post)
|
||||||
|
- [Sending payloads in the Body](#user-content-sending-payloads-in-the-body)
|
||||||
|
- [Specifiying request headers](#user-content-specifiying-request-headers)
|
||||||
|
- [Sending Cookies](#cookie-support)
|
||||||
|
- [Setting timeouts](#user-content-setting-timeouts)
|
||||||
|
- [Using the Response and Error](#user-content-using-the-response-and-error)
|
||||||
|
- [Receiving JSON](#user-content-receiving-json)
|
||||||
|
- [Sending/Receiving Compressed Payloads](#user-content-sendingreceiving-compressed-payloads)
|
||||||
|
- [Using gzip compression:](#user-content-using-gzip-compression)
|
||||||
|
- [Using deflate compression:](#user-content-using-deflate-compression)
|
||||||
|
- [Using compressed responses:](#user-content-using-compressed-responses)
|
||||||
|
- [Proxy](#proxy)
|
||||||
|
- [Debugging requests](#debug)
|
||||||
|
- [Getting raw Request & Response](#getting-raw-request--response)
|
||||||
|
- [TODO:](#user-content-todo)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Why GoReq?
|
||||||
|
==========
|
||||||
|
|
||||||
|
Go has very nice native libraries that allows you to do lots of cool things. But sometimes those libraries are too low level, which means that to do a simple thing, like an HTTP Request, it takes some time. And if you want to do something as simple as adding a timeout to a request, you will end up writing several lines of code.
|
||||||
|
|
||||||
|
This is why we think GoReq is useful. Because you can do all your HTTP requests in a very simple and comprehensive way, while enabling you to do more advanced stuff by giving you access to the native API.
|
||||||
|
|
||||||
|
How do I install it?
|
||||||
|
====================
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/franela/goreq
|
||||||
|
```
|
||||||
|
|
||||||
|
What can I do with it?
|
||||||
|
======================
|
||||||
|
|
||||||
|
## Making requests with different methods
|
||||||
|
|
||||||
|
#### GET
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{ Uri: "http://www.google.com" }.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
GoReq default method is GET.
|
||||||
|
|
||||||
|
You can also set value to GET method easily
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Item struct {
|
||||||
|
Limit int
|
||||||
|
Skip int
|
||||||
|
Fields string
|
||||||
|
}
|
||||||
|
|
||||||
|
item := Item {
|
||||||
|
Limit: 3,
|
||||||
|
Skip: 5,
|
||||||
|
Fields: "Value",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://localhost:3000/",
|
||||||
|
QueryString: item,
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
The sample above will send `http://localhost:3000/?limit=3&skip=5&fields=Value`
|
||||||
|
|
||||||
|
Alternatively the `url` tag can be used in struct fields to customize encoding properties
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Item struct {
|
||||||
|
TheLimit int `url:"the_limit"`
|
||||||
|
TheSkip string `url:"the_skip,omitempty"`
|
||||||
|
TheFields string `url:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
item := Item {
|
||||||
|
TheLimit: 3,
|
||||||
|
TheSkip: "",
|
||||||
|
TheFields: "Value",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://localhost:3000/",
|
||||||
|
QueryString: item,
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
The sample above will send `http://localhost:3000/?the_limit=3`
|
||||||
|
|
||||||
|
|
||||||
|
QueryString also support url.Values
|
||||||
|
|
||||||
|
```go
|
||||||
|
item := url.Values{}
|
||||||
|
item.Set("Limit", 3)
|
||||||
|
item.Add("Field", "somefield")
|
||||||
|
item.Add("Field", "someotherfield")
|
||||||
|
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://localhost:3000/",
|
||||||
|
QueryString: item,
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
The sample above will send `http://localhost:3000/?limit=3&field=somefield&field=someotherfield`
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
Struct field `url` tag is mainly used as the request parameter name.
|
||||||
|
Tags can be comma separated multiple values, 1st value is for naming and rest has special meanings.
|
||||||
|
|
||||||
|
- special tag for 1st value
|
||||||
|
- `-`: value is ignored if set this
|
||||||
|
|
||||||
|
- special tag for rest 2nd value
|
||||||
|
- `omitempty`: zero-value is ignored if set this
|
||||||
|
- `squash`: the fields of embedded struct is used for parameter
|
||||||
|
|
||||||
|
#### Tag Examples
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Place struct {
|
||||||
|
Country string `url:"country"`
|
||||||
|
City string `url:"city"`
|
||||||
|
ZipCode string `url:"zipcode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Place `url:",squash"`
|
||||||
|
|
||||||
|
FirstName string `url:"first_name"`
|
||||||
|
LastName string `url:"last_name"`
|
||||||
|
Age string `url:"age,omitempty"`
|
||||||
|
Password string `url:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
johnbull := Person{
|
||||||
|
Place: Place{ // squash the embedded struct value
|
||||||
|
Country: "UK",
|
||||||
|
City: "London",
|
||||||
|
ZipCode: "SW1",
|
||||||
|
},
|
||||||
|
FirstName: "John",
|
||||||
|
LastName: "Doe",
|
||||||
|
Age: "35",
|
||||||
|
Password: "my-secret", // ignored for parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
goreq.Request{
|
||||||
|
Uri: "http://localhost/",
|
||||||
|
QueryString: johnbull,
|
||||||
|
}.Do()
|
||||||
|
// => `http://localhost/?first_name=John&last_name=Doe&age=35&country=UK&city=London&zip_code=SW1`
|
||||||
|
|
||||||
|
|
||||||
|
// age and zipcode will be ignored because of `omitempty`
|
||||||
|
// but firstname isn't.
|
||||||
|
samurai := Person{
|
||||||
|
Place: Place{ // squash the embedded struct value
|
||||||
|
Country: "Japan",
|
||||||
|
City: "Tokyo",
|
||||||
|
},
|
||||||
|
LastName: "Yagyu",
|
||||||
|
}
|
||||||
|
|
||||||
|
goreq.Request{
|
||||||
|
Uri: "http://localhost/",
|
||||||
|
QueryString: samurai,
|
||||||
|
}.Do()
|
||||||
|
// => `http://localhost/?first_name=&last_name=yagyu&country=Japan&city=Tokyo`
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### POST
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{ Method: "POST", Uri: "http://www.google.com" }.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending payloads in the Body
|
||||||
|
|
||||||
|
You can send ```string```, ```Reader``` or ```interface{}``` in the body. The first two will be sent as text. The last one will be marshalled to JSON, if possible.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Item struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
item := Item{ Id: 1111, Name: "foobar" }
|
||||||
|
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Body: item,
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specifiying request headers
|
||||||
|
|
||||||
|
We think that most of the times the request headers that you use are: ```Host```, ```Content-Type```, ```Accept``` and ```User-Agent```. This is why we decided to make it very easy to set these headers.
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Host: "foobar.com",
|
||||||
|
Accept: "application/json",
|
||||||
|
ContentType: "application/json",
|
||||||
|
UserAgent: "goreq",
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
But sometimes you need to set other headers. You can still do it.
|
||||||
|
|
||||||
|
```go
|
||||||
|
req := goreq.Request{ Uri: "http://www.google.com" }
|
||||||
|
|
||||||
|
req.AddHeader("X-Custom", "somevalue")
|
||||||
|
|
||||||
|
req.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively you can use the `WithHeader` function to keep the syntax short
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err = goreq.Request{ Uri: "http://www.google.com" }.WithHeader("X-Custom", "somevalue").Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cookie support
|
||||||
|
|
||||||
|
Cookies can be either set at the request level by sending a [CookieJar](http://golang.org/pkg/net/http/cookiejar/) in the `CookieJar` request field
|
||||||
|
or you can use goreq's one-liner WithCookie method as shown below
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
}.
|
||||||
|
WithCookie(&http.Cookie{Name: "c1", Value: "v1"}).
|
||||||
|
Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting timeouts
|
||||||
|
|
||||||
|
GoReq supports 2 kind of timeouts. A general connection timeout and a request specific one. By default the connection timeout is of 1 second. There is no default for request timeout, which means it will wait forever.
|
||||||
|
|
||||||
|
You can change the connection timeout doing:
|
||||||
|
|
||||||
|
```go
|
||||||
|
goreq.SetConnectTimeout(100 * time.Millisecond)
|
||||||
|
```
|
||||||
|
|
||||||
|
And specify the request timeout doing:
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Timeout: 500 * time.Millisecond,
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Response and Error
|
||||||
|
|
||||||
|
GoReq will always return 2 values: a ```Response``` and an ```Error```.
|
||||||
|
If ```Error``` is not ```nil``` it means that an error happened while doing the request and you shouldn't use the ```Response``` in any way.
|
||||||
|
You can check what happened by getting the error message:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
```
|
||||||
|
And to make it easy to know if it was a timeout error, you can ask the error or return it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if serr, ok := err.(*goreq.Error); ok {
|
||||||
|
if serr.Timeout() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't get an error, you can safely use the ```Response```.
|
||||||
|
|
||||||
|
```go
|
||||||
|
res.Uri // return final URL location of the response (fulfilled after redirect was made)
|
||||||
|
res.StatusCode // return the status code of the response
|
||||||
|
res.Body // gives you access to the body
|
||||||
|
res.Body.ToString() // will return the body as a string
|
||||||
|
res.Header.Get("Content-Type") // gives you access to all the response headers
|
||||||
|
```
|
||||||
|
Remember that you should **always** close `res.Body` if it's not `nil`
|
||||||
|
|
||||||
|
## Receiving JSON
|
||||||
|
|
||||||
|
GoReq will help you to receive and unmarshal JSON.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Item struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var item Item
|
||||||
|
|
||||||
|
res.Body.FromJsonTo(&item)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending/Receiving Compressed Payloads
|
||||||
|
GoReq supports gzip, deflate and zlib compression of requests' body and transparent decompression of responses provided they have a correct `Content-Encoding` header.
|
||||||
|
|
||||||
|
#####Using gzip compression:
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Body: item,
|
||||||
|
Compression: goreq.Gzip(),
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
#####Using deflate/zlib compression:
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Body: item,
|
||||||
|
Compression: goreq.Deflate(),
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
#####Using compressed responses:
|
||||||
|
If servers replies a correct and matching `Content-Encoding` header (gzip requires `Content-Encoding: gzip` and deflate `Content-Encoding: deflate`) goreq transparently decompresses the response so the previous example should always work:
|
||||||
|
```go
|
||||||
|
type Item struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Body: item,
|
||||||
|
Compression: goreq.Gzip(),
|
||||||
|
}.Do()
|
||||||
|
var item Item
|
||||||
|
res.Body.FromJsonTo(&item)
|
||||||
|
```
|
||||||
|
If no `Content-Encoding` header is replied by the server GoReq will return the crude response.
|
||||||
|
|
||||||
|
## Proxy
|
||||||
|
If you need to use a proxy for your requests GoReq supports the standard `http_proxy` env variable as well as manually setting the proxy for each request
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Proxy: "http://myproxy:myproxyport",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proxy basic auth is also supported
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Proxy: "http://user:pass@myproxy:myproxyport",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
}.Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debug
|
||||||
|
If you need to debug your http requests, it can print the http request detail.
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Compression: goreq.Gzip(),
|
||||||
|
ShowDebug: true,
|
||||||
|
}.Do()
|
||||||
|
fmt.Println(res, err)
|
||||||
|
```
|
||||||
|
|
||||||
|
and it will print the log:
|
||||||
|
```
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: www.google.com
|
||||||
|
Accept:
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
Content-Encoding: gzip
|
||||||
|
Content-Type:
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Getting raw Request & Response
|
||||||
|
|
||||||
|
To get the Request:
|
||||||
|
|
||||||
|
```go
|
||||||
|
req := goreq.Request{
|
||||||
|
Host: "foobar.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
//req.Request will return a new instance of an http.Request so you can safely use it for something else
|
||||||
|
request, _ := req.NewRequest()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
To get the Response:
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := goreq.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Uri: "http://www.google.com",
|
||||||
|
Compression: goreq.Gzip(),
|
||||||
|
ShowDebug: true,
|
||||||
|
}.Do()
|
||||||
|
|
||||||
|
// res.Response will contain the original http.Response structure
|
||||||
|
fmt.Println(res.Response, err)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
-----
|
||||||
|
|
||||||
|
We do have a couple of [issues](https://github.com/franela/goreq/issues) pending we'll be addressing soon. But feel free to
|
||||||
|
contribute and send us PRs (with tests please :smile:).
|
|
@ -0,0 +1,491 @@
|
||||||
|
package goreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"compress/zlib"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type itimeout interface {
|
||||||
|
Timeout() bool
|
||||||
|
}
|
||||||
|
type Request struct {
|
||||||
|
headers []headerTuple
|
||||||
|
cookies []*http.Cookie
|
||||||
|
Method string
|
||||||
|
Uri string
|
||||||
|
Body interface{}
|
||||||
|
QueryString interface{}
|
||||||
|
Timeout time.Duration
|
||||||
|
ContentType string
|
||||||
|
Accept string
|
||||||
|
Host string
|
||||||
|
UserAgent string
|
||||||
|
Insecure bool
|
||||||
|
MaxRedirects int
|
||||||
|
RedirectHeaders bool
|
||||||
|
Proxy string
|
||||||
|
Compression *compression
|
||||||
|
BasicAuthUsername string
|
||||||
|
BasicAuthPassword string
|
||||||
|
CookieJar http.CookieJar
|
||||||
|
ShowDebug bool
|
||||||
|
OnBeforeRequest func(goreq *Request, httpreq *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type compression struct {
|
||||||
|
writer func(buffer io.Writer) (io.WriteCloser, error)
|
||||||
|
reader func(buffer io.Reader) (io.ReadCloser, error)
|
||||||
|
ContentEncoding string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
*http.Response
|
||||||
|
Uri string
|
||||||
|
Body *Body
|
||||||
|
req *http.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Response) CancelRequest() {
|
||||||
|
cancelRequest(DefaultTransport, r.req)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelRequest(transport interface{}, r *http.Request) {
|
||||||
|
if tp, ok := transport.(transportRequestCanceler); ok {
|
||||||
|
tp.CancelRequest(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerTuple struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
reader io.ReadCloser
|
||||||
|
compressedReader io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
timeout bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type transportRequestCanceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Timeout() bool {
|
||||||
|
return e.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) Read(p []byte) (int, error) {
|
||||||
|
if b.compressedReader != nil {
|
||||||
|
return b.compressedReader.Read(p)
|
||||||
|
}
|
||||||
|
return b.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) Close() error {
|
||||||
|
err := b.reader.Close()
|
||||||
|
if b.compressedReader != nil {
|
||||||
|
return b.compressedReader.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) FromJsonTo(o interface{}) error {
|
||||||
|
return json.NewDecoder(b).Decode(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) ToString() (string, error) {
|
||||||
|
body, err := ioutil.ReadAll(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gzip() *compression {
|
||||||
|
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||||
|
return gzip.NewReader(buffer)
|
||||||
|
}
|
||||||
|
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||||
|
return gzip.NewWriter(buffer), nil
|
||||||
|
}
|
||||||
|
return &compression{writer: writer, reader: reader, ContentEncoding: "gzip"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deflate() *compression {
|
||||||
|
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||||
|
return zlib.NewReader(buffer)
|
||||||
|
}
|
||||||
|
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||||
|
return zlib.NewWriter(buffer), nil
|
||||||
|
}
|
||||||
|
return &compression{writer: writer, reader: reader, ContentEncoding: "deflate"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Zlib() *compression {
|
||||||
|
return Deflate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func paramParse(query interface{}) (string, error) {
|
||||||
|
switch query.(type) {
|
||||||
|
case url.Values:
|
||||||
|
return query.(url.Values).Encode(), nil
|
||||||
|
case *url.Values:
|
||||||
|
return query.(*url.Values).Encode(), nil
|
||||||
|
default:
|
||||||
|
var v = &url.Values{}
|
||||||
|
err := paramParseStruct(v, query)
|
||||||
|
return v.Encode(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func paramParseStruct(v *url.Values, query interface{}) error {
|
||||||
|
var (
|
||||||
|
s = reflect.ValueOf(query)
|
||||||
|
t = reflect.TypeOf(query)
|
||||||
|
)
|
||||||
|
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface {
|
||||||
|
s = s.Elem()
|
||||||
|
t = s.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return errors.New("Can not parse QueryString.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
field := s.Field(i)
|
||||||
|
typeField := t.Field(i)
|
||||||
|
|
||||||
|
if !field.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
urlTag := typeField.Tag.Get("url")
|
||||||
|
if urlTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name, opts := parseTag(urlTag)
|
||||||
|
|
||||||
|
var omitEmpty, squash bool
|
||||||
|
omitEmpty = opts.Contains("omitempty")
|
||||||
|
squash = opts.Contains("squash")
|
||||||
|
|
||||||
|
if squash {
|
||||||
|
err := paramParseStruct(v, field.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if urlTag == "" {
|
||||||
|
name = strings.ToLower(typeField.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := fmt.Sprintf("%v", field.Interface()); !(omitEmpty && len(val) == 0) {
|
||||||
|
v.Add(name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRequestBody(b interface{}) (io.Reader, error) {
|
||||||
|
switch b.(type) {
|
||||||
|
case string:
|
||||||
|
// treat is as text
|
||||||
|
return strings.NewReader(b.(string)), nil
|
||||||
|
case io.Reader:
|
||||||
|
// treat is as text
|
||||||
|
return b.(io.Reader), nil
|
||||||
|
case []byte:
|
||||||
|
//treat as byte array
|
||||||
|
return bytes.NewReader(b.([]byte)), nil
|
||||||
|
case nil:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
// try to jsonify it
|
||||||
|
j, err := json.Marshal(b)
|
||||||
|
if err == nil {
|
||||||
|
return bytes.NewReader(j), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultDialer = &net.Dialer{Timeout: 1000 * time.Millisecond}
|
||||||
|
var DefaultTransport http.RoundTripper = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyFromEnvironment}
|
||||||
|
var DefaultClient = &http.Client{Transport: DefaultTransport}
|
||||||
|
|
||||||
|
var proxyTransport http.RoundTripper
|
||||||
|
var proxyClient *http.Client
|
||||||
|
|
||||||
|
func SetConnectTimeout(duration time.Duration) {
|
||||||
|
DefaultDialer.Timeout = duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) AddHeader(name string, value string) {
|
||||||
|
if r.headers == nil {
|
||||||
|
r.headers = []headerTuple{}
|
||||||
|
}
|
||||||
|
r.headers = append(r.headers, headerTuple{name: name, value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) WithHeader(name string, value string) Request {
|
||||||
|
r.AddHeader(name, value)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) AddCookie(c *http.Cookie) {
|
||||||
|
r.cookies = append(r.cookies, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) WithCookie(c *http.Cookie) Request {
|
||||||
|
r.AddCookie(c)
|
||||||
|
return r
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) Do() (*Response, error) {
|
||||||
|
var client = DefaultClient
|
||||||
|
var transport = DefaultTransport
|
||||||
|
var resUri string
|
||||||
|
var redirectFailed bool
|
||||||
|
|
||||||
|
r.Method = valueOrDefault(r.Method, "GET")
|
||||||
|
|
||||||
|
// use a client with a cookie jar if necessary. We create a new client not
|
||||||
|
// to modify the default one.
|
||||||
|
if r.CookieJar != nil {
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Jar: r.CookieJar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Proxy != "" {
|
||||||
|
proxyUrl, err := url.Parse(r.Proxy)
|
||||||
|
if err != nil {
|
||||||
|
// proxy address is in a wrong format
|
||||||
|
return nil, &Error{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If jar is specified new client needs to be built
|
||||||
|
if proxyTransport == nil || client.Jar != nil {
|
||||||
|
proxyTransport = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyURL(proxyUrl)}
|
||||||
|
proxyClient = &http.Client{Transport: proxyTransport, Jar: client.Jar}
|
||||||
|
} else if proxyTransport, ok := proxyTransport.(*http.Transport); ok {
|
||||||
|
proxyTransport.Proxy = http.ProxyURL(proxyUrl)
|
||||||
|
}
|
||||||
|
transport = proxyTransport
|
||||||
|
client = proxyClient
|
||||||
|
}
|
||||||
|
|
||||||
|
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
|
||||||
|
if len(via) > r.MaxRedirects {
|
||||||
|
redirectFailed = true
|
||||||
|
return errors.New("Error redirecting. MaxRedirects reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
resUri = req.URL.String()
|
||||||
|
|
||||||
|
//By default Golang will not redirect request headers
|
||||||
|
// https://code.google.com/p/go/issues/detail?id=4800&q=request%20header
|
||||||
|
if r.RedirectHeaders {
|
||||||
|
for key, val := range via[0].Header {
|
||||||
|
req.Header[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if transport, ok := transport.(*http.Transport); ok {
|
||||||
|
if r.Insecure {
|
||||||
|
if transport.TLSClientConfig != nil {
|
||||||
|
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||||
|
} else {
|
||||||
|
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
} else if transport.TLSClientConfig != nil {
|
||||||
|
// the default TLS client (when transport.TLSClientConfig==nil) is
|
||||||
|
// already set to verify, so do nothing in that case
|
||||||
|
transport.TLSClientConfig.InsecureSkipVerify = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := r.NewRequest()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// we couldn't parse the URL.
|
||||||
|
return nil, &Error{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := false
|
||||||
|
if r.Timeout > 0 {
|
||||||
|
client.Timeout = r.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ShowDebug {
|
||||||
|
dump, err := httputil.DumpRequest(req, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
log.Println(string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.OnBeforeRequest != nil {
|
||||||
|
r.OnBeforeRequest(&r, req)
|
||||||
|
}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !timeout {
|
||||||
|
if t, ok := err.(itimeout); ok {
|
||||||
|
timeout = t.Timeout()
|
||||||
|
}
|
||||||
|
if ue, ok := err.(*url.Error); ok {
|
||||||
|
if t, ok := ue.Err.(itimeout); ok {
|
||||||
|
timeout = t.Timeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *Response
|
||||||
|
//If redirect fails we still want to return response data
|
||||||
|
if redirectFailed {
|
||||||
|
if res != nil {
|
||||||
|
response = &Response{res, resUri, &Body{reader: res.Body}, req}
|
||||||
|
} else {
|
||||||
|
response = &Response{res, resUri, nil, req}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If redirect fails and we haven't set a redirect count we shouldn't return an error
|
||||||
|
if redirectFailed && r.MaxRedirects == 0 {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, &Error{timeout: timeout, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Compression != nil && strings.Contains(res.Header.Get("Content-Encoding"), r.Compression.ContentEncoding) {
|
||||||
|
compressedReader, err := r.Compression.reader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &Error{Err: err}
|
||||||
|
}
|
||||||
|
return &Response{res, resUri, &Body{reader: res.Body, compressedReader: compressedReader}, req}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{res, resUri, &Body{reader: res.Body}, req}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) addHeaders(headersMap http.Header) {
|
||||||
|
if len(r.UserAgent) > 0 {
|
||||||
|
headersMap.Add("User-Agent", r.UserAgent)
|
||||||
|
}
|
||||||
|
if r.Accept != "" {
|
||||||
|
headersMap.Add("Accept", r.Accept)
|
||||||
|
}
|
||||||
|
if r.ContentType != "" {
|
||||||
|
headersMap.Add("Content-Type", r.ContentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) NewRequest() (*http.Request, error) {
|
||||||
|
|
||||||
|
b, e := prepareRequestBody(r.Body)
|
||||||
|
if e != nil {
|
||||||
|
// there was a problem marshaling the body
|
||||||
|
return nil, &Error{Err: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.QueryString != nil {
|
||||||
|
param, e := paramParse(r.QueryString)
|
||||||
|
if e != nil {
|
||||||
|
return nil, &Error{Err: e}
|
||||||
|
}
|
||||||
|
r.Uri = r.Uri + "?" + param
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyReader io.Reader
|
||||||
|
if b != nil && r.Compression != nil {
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
readBuffer := bufio.NewReader(b)
|
||||||
|
writer, err := r.Compression.writer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &Error{Err: err}
|
||||||
|
}
|
||||||
|
_, e = readBuffer.WriteTo(writer)
|
||||||
|
writer.Close()
|
||||||
|
if e != nil {
|
||||||
|
return nil, &Error{Err: e}
|
||||||
|
}
|
||||||
|
bodyReader = buffer
|
||||||
|
} else {
|
||||||
|
bodyReader = b
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(r.Method, r.Uri, bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// add headers to the request
|
||||||
|
req.Host = r.Host
|
||||||
|
|
||||||
|
r.addHeaders(req.Header)
|
||||||
|
if r.Compression != nil {
|
||||||
|
req.Header.Add("Content-Encoding", r.Compression.ContentEncoding)
|
||||||
|
req.Header.Add("Accept-Encoding", r.Compression.ContentEncoding)
|
||||||
|
}
|
||||||
|
if r.headers != nil {
|
||||||
|
for _, header := range r.headers {
|
||||||
|
req.Header.Add(header.name, header.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//use basic auth if required
|
||||||
|
if r.BasicAuthUsername != "" {
|
||||||
|
req.SetBasicAuth(r.BasicAuthUsername, r.BasicAuthPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range r.cookies {
|
||||||
|
req.AddCookie(c)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return value if nonempty, def otherwise.
|
||||||
|
func valueOrDefault(value, def string) string {
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found here: https://github.com/golang/go/blob/master/LICENSE
|
||||||
|
|
||||||
|
package goreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(o)
|
||||||
|
for s != "" {
|
||||||
|
var next string
|
||||||
|
i := strings.Index(s, ",")
|
||||||
|
if i >= 0 {
|
||||||
|
s, next = s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s = next
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidTag(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
switch {
|
||||||
|
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
||||||
|
// Backslash and quote chars are reserved, but
|
||||||
|
// otherwise any punctuation chars are allowed
|
||||||
|
// in a tag name.
|
||||||
|
default:
|
||||||
|
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
# How to contribute
|
||||||
|
|
||||||
|
Your contributions are more than welcomed at OpsGenie! You can contribute to OpsGenie Go SDK by submitting a pull request.
|
||||||
|
|
||||||
|
Before pushing your commits, please make sure you followed the steps described below:
|
||||||
|
|
||||||
|
1. Run `go fmt` to format your code.
|
||||||
|
2. Alternatively you can use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), it also formats import lines.
|
||||||
|
3. [golint](https://github.com/golang/lint) your code to detect style mistakes.
|
||||||
|
4. [govet](http://godoc.org/golang.org/x/tools/cmd/vet) your code to detect suspicious constructs.
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2015 OpsGenie
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,5 @@
|
||||||
|
OpsGenie Go SDK
|
||||||
|
Copyright 2015 OpsGenie
|
||||||
|
|
||||||
|
This product includes software developed at
|
||||||
|
OpsGenie (http://www.opsgenie.com/).
|
|
@ -0,0 +1,97 @@
|
||||||
|
# opsgenie-go-sdk
|
||||||
|
## Aim and Scope
|
||||||
|
OpsGenie GO SDK aims to access OpsGenie Web API through HTTP calls
|
||||||
|
from a client application purely written in Go language.
|
||||||
|
|
||||||
|
OpsGenie Go SDK covers *the Alert API*, *the Heartbeat API*,
|
||||||
|
*the Integration API* and *the Policy API*. Future releases
|
||||||
|
are subject to be delivered for packing more APIs soon.
|
||||||
|
|
||||||
|
**Documentation:** [![](https://godoc.org/github.com/nathany/looper?status.svg)](http://godoc.org/github.com/opsgenie/opsgenie-go-sdk/client)
|
||||||
|
|
||||||
|
For more information about OpsGenie Go SDK, please refer to [OpsGenie Go API](https://www.opsgenie.com/docs/api-and-client-libraries/opsgenie-go-api) document.
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
* The API is built using Go 1.4.2. Some features may not be
|
||||||
|
available or supported unless you have installed a relevant version of Go.
|
||||||
|
Please click [https://golang.org/dl/](https://golang.org/dl/) to download and
|
||||||
|
get more information about installing Go on your computer.
|
||||||
|
* Make sure you have properly set both `GOROOT` and `GOPATH`
|
||||||
|
environment variables.
|
||||||
|
|
||||||
|
* Before you can begin, you need to sign up [OpsGenie](http://www.opsgenie.com) if you
|
||||||
|
don't have a valid account yet. Create an API Integration and get your API key.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
To download all packages in the repo with their dependencies, simply run
|
||||||
|
|
||||||
|
`go get github.com/opsgenie/opsgenie-go-sdk/...`
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
One can start using OpsGenie Go SDK by initializing client and making a request. Example shown below demonstrates how to initialize an OpsGenie Alert client and make a create alert request.
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
alerts "github.com/opsgenie/opsgenie-go-sdk/alerts"
|
||||||
|
ogcli "github.com/opsgenie/opsgenie-go-sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cli := new(ogcli.OpsGenieClient)
|
||||||
|
cli.SetAPIKey("YOUR_API_KEY")
|
||||||
|
|
||||||
|
alertCli, cliErr := cli.Alert()
|
||||||
|
|
||||||
|
if cliErr != nil {
|
||||||
|
panic(cliErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the alert
|
||||||
|
req := alerts.CreateAlertRequest{Message: "Hello from OpsGenie Go Sdk"}
|
||||||
|
response, alertErr := alertCli.Create(req)
|
||||||
|
|
||||||
|
if alertErr != nil {
|
||||||
|
panic(alertErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("message: %s\n", response.Message)
|
||||||
|
fmt.Printf("alert id: %s\n", response.AlertId)
|
||||||
|
fmt.Printf("status: %s\n", response.Status)
|
||||||
|
fmt.Printf("code: %d\n", response.Code)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
There are many useful sample code snippets under `samples` directory for packages.
|
||||||
|
|
||||||
|
## Handling Zero value problem with 'omitempty' option in Json
|
||||||
|
|
||||||
|
Every golang type has a [zero value](http://golang.org/ref/spec#The_zero_value).
|
||||||
|
AddHeartbeat and UpdateHeartbeat requests have a boolean "Enabled" field to determine a heartbeat is enabled or disabled.
|
||||||
|
enabled is not a mandatory field in both requests so, it has "omitempty" flag.
|
||||||
|
|
||||||
|
When JSON is unmarshalling the requests, it omits the fields contains zero value of its type when this option is set.
|
||||||
|
|
||||||
|
The problem starts here:
|
||||||
|
When you want to set the Enabled field as false, JSON does not unmarshal it because it contains boolean zero value and has option omitempty.
|
||||||
|
This problem occurs with strings; when you want to reset a heartbeat's description. To set heartbeat's description as empty string, you should make the request with Description :"".
|
||||||
|
But JSON does not unmarshal it either.
|
||||||
|
|
||||||
|
So, to solve this we followed go-github's solution as mentioned [here](https://willnorris.com/2014/05/go-rest-apis-and-pointers).
|
||||||
|
We used pointers just for booleans, if you want to set a string's value to empty. please use " ", or "-" as new string value.
|
||||||
|
|
||||||
|
## The Web API
|
||||||
|
|
||||||
|
Please follow the links below for more information and details
|
||||||
|
about the Web API.
|
||||||
|
|
||||||
|
* [Alert API](https://www.opsgenie.com/docs/web-api/alert-api)
|
||||||
|
* [Heartbeat API](https://www.opsgenie.com/docs/web-api/heartbeat-api)
|
||||||
|
* [Integration API](https://www.opsgenie.com/docs/web-api/integration-api)
|
||||||
|
* [Policy API](https://www.opsgenie.com/docs/web-api/policy-api)
|
||||||
|
|
||||||
|
|
||||||
|
## Bug Reporting and Feature Requests
|
||||||
|
|
||||||
|
If you like to report a bug, or a feature request; please open an issue.
|
221
vendor/github.com/opsgenie/opsgenie-go-sdk/alerts/alert_responses.go
generated
vendored
Normal file
221
vendor/github.com/opsgenie/opsgenie-go-sdk/alerts/alert_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
package alerts
|
||||||
|
|
||||||
|
// CreateAlertResponse holds the result data of the CreateAlertRequest
|
||||||
|
type CreateAlertResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
AlertID string `json:"alertId"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountAlertResponse holds the result data of the CountAlertRequest
|
||||||
|
type CountAlertResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseAlertResponse holds the result data of the CloseAlertRequest
|
||||||
|
type CloseAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlertResponse holds the result data of the DeleteAlertRequest
|
||||||
|
type DeleteAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertsResponse holds the result data of the ListAlertsRequest
|
||||||
|
type ListAlertsResponse struct {
|
||||||
|
Alerts []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
IsSeen bool `json:"isSeen"`
|
||||||
|
Acknowledged bool `json:"acknowledged"`
|
||||||
|
CreatedAt uint64 `json:"createdAt"`
|
||||||
|
UpdatedAt uint64 `json:"updatedAt"`
|
||||||
|
TinyID string `json:"tinyId"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
} `json:"alerts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertNotesResponse holds the result data of the ListAlertNotesRequest
|
||||||
|
type ListAlertNotesResponse struct {
|
||||||
|
Took int `json:"took"`
|
||||||
|
LastKey string `json:"lastKey"`
|
||||||
|
Notes []struct {
|
||||||
|
Note string `json:"note"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
CreatedAt uint64 `json:"createdAt"`
|
||||||
|
} `json:"notes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertLogsResponse holds the result data of the ListAlertLogsRequest
|
||||||
|
type ListAlertLogsResponse struct {
|
||||||
|
LastKey string `json:"lastKey"`
|
||||||
|
Logs []struct {
|
||||||
|
Log string `json:"log"`
|
||||||
|
LogType string `json:"logType"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
CreatedAt uint64 `json:"createdAt"`
|
||||||
|
} `json:"logs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertRecipientsResponse holds the result data of the ListAlertRecipientsRequest.
|
||||||
|
type ListAlertRecipientsResponse struct {
|
||||||
|
Users []struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
StateChangedAt uint64 `json:"stateChangedAt"`
|
||||||
|
} `json:"users"`
|
||||||
|
|
||||||
|
Groups map[string][]struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
StateChangedAt uint64 `json:"stateChangedAt"`
|
||||||
|
} `json:"groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcknowledgeAlertResponse holds the result data of the AcknowledgeAlertRequest.
|
||||||
|
type AcknowledgeAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenotifyAlertResponse holds the result data of the RenotifyAlertRequest.
|
||||||
|
type RenotifyAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeOwnershipAlertResponse holds the result data of the TakeOwnershipAlertRequest.
|
||||||
|
type TakeOwnershipAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignOwnerAlertResponse holds the result data of the AssignOwnerAlertRequest.
|
||||||
|
type AssignOwnerAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeamAlertResponse holds the result data of the AddTeamAlertRequest.
|
||||||
|
type AddTeamAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRecipientAlertResponse holds the result data of the AddRecipientAlertRequest.
|
||||||
|
type AddRecipientAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNoteAlertResponse holds the result data of the AddNoteAlertRequest.
|
||||||
|
type AddNoteAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTagsAlertResponse holds the result data of the AddTagsAlertRequest.
|
||||||
|
type AddTagsAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteActionAlertResponse holds the result data of the ExecuteActionAlertRequest.
|
||||||
|
type ExecuteActionAlertResponse struct {
|
||||||
|
Result string `json:"result"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFileAlertResponse holds the result data of the AttachFileAlertRequest.
|
||||||
|
type AttachFileAlertResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlertResponse holds the result data of the GetAlertRequest.
|
||||||
|
type GetAlertResponse struct {
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Teams []string `json:"teams"`
|
||||||
|
Recipients []string `json:"recipients"`
|
||||||
|
TinyID string `json:"tinyId"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Entity string `json:"entity"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
UpdatedAt uint64 `json:"updatedAt"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Details map[string]string `json:"details"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt uint64 `json:"createdAt"`
|
||||||
|
IsSeen bool `json:"isSeen"`
|
||||||
|
Acknowledged bool `json:"acknowledged"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Actions []string `json:"actions"`
|
||||||
|
SystemData map[string]interface{} `json:"systemData"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//IntegrationType returns extracted "integrationType" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) IntegrationType() string {
|
||||||
|
if val, ok := res.SystemData["integrationType"].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//IntegrationID returns extracted "integrationId" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) IntegrationID() string {
|
||||||
|
if val, ok := res.SystemData["integrationId"].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//IntegrationName returns extracted "integrationName" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) IntegrationName() string {
|
||||||
|
if val, ok := res.SystemData["integrationName"].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//AckTime returns extracted "ackTime" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) AckTime() uint64 {
|
||||||
|
if val, ok := res.SystemData["ackTime"].(uint64); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//AcknowledgedBy returns extracted "acknowledgedBy" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) AcknowledgedBy() string {
|
||||||
|
if val, ok := res.SystemData["acknowledgedBy"].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//CloseTime returns extracted "closeTime" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) CloseTime() uint64 {
|
||||||
|
if val, ok := res.SystemData["closeTime"].(uint64); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//ClosedBy returns extracted "closedBy" data from the retrieved alert' SystemData property.
|
||||||
|
func (res *GetAlertResponse) ClosedBy() string {
|
||||||
|
if val, ok := res.SystemData["closedBy"].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package alerts provides requests and response structures to achieve Alert API actions.
|
||||||
|
package alerts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AcknowledgeAlertRequest provides necessary parameter structure to Acknowledge an alert at OpsGenie.
|
||||||
|
type AcknowledgeAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNoteAlertRequest provides necessary parameter structure to Add Note to an alert at OpsGenie.
|
||||||
|
type AddNoteAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRecipientAlertRequest provides necessary parameter structure to Add Recipient to an alert at OpsGenie.
|
||||||
|
type AddRecipientAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Recipient string `json:"recipient,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeamAlertRequest provides necessary parameter structure to Add Team to an alert at OpsGenie.
|
||||||
|
type AddTeamAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Team string `json:"team,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTagsAlertRequest provides necessary parameter structure to Add Tags to an alert at OpsGenie.
|
||||||
|
type AddTagsAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignOwnerAlertRequest provides necessary parameter structure to Assign a User as Owner to an alert at OpsGenie.
|
||||||
|
type AssignOwnerAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Owner string `json:"owner,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFileAlertRequest provides necessary parameter structure to Attach File to an alert at OpsGenie.
|
||||||
|
type AttachFileAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Attachment *os.File `json:"attachment,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
IndexFile string `json:"indexFile,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseAlertRequest provides necessary parameter structure to Close an alert at OpsGenie.
|
||||||
|
type CloseAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Notify []string `json:"notify,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAlertRequest provides necessary parameter structure to Create an alert at OpsGenie.
|
||||||
|
type CreateAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Teams []string `json:"teams,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Recipients []string `json:"recipients,omitempty"`
|
||||||
|
Actions []string `json:"actions,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Details map[string]string `json:"details,omitempty"`
|
||||||
|
Entity string `json:"entity,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlertRequest provides necessary parameter structure to Delete an alert from OpsGenie.
|
||||||
|
type DeleteAlertRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Alias string `url:"alias,omitempty"`
|
||||||
|
User string `url:"user,omitempty"`
|
||||||
|
Source string `url:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteActionAlertRequest provides necessary parameter structure to Execute Custom Actions on an alert at OpsGenie.
|
||||||
|
type ExecuteActionAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Action string `json:"action,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlertRequest provides necessary parameter structure to Retrieve an alert details from OpsGenie.
|
||||||
|
type GetAlertRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Alias string `url:"alias,omitempty"`
|
||||||
|
TinyID string `url:"tinyId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertLogsRequest provides necessary parameter structure to Retrieve activity logs of an alert from OpsGenie.
|
||||||
|
type ListAlertLogsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Alias string `url:"alias,omitempty"`
|
||||||
|
Limit uint64 `url:"limit,omitempty"`
|
||||||
|
Order string `url:"order,omitempty"`
|
||||||
|
LastKey string `url:"lastKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertNotesRequest provides necessary parameter structure to Retrieve notes of an alert from OpsGenie.
|
||||||
|
type ListAlertNotesRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Alias string `url:"alias,omitempty"`
|
||||||
|
Limit uint64 `url:"limit,omitempty"`
|
||||||
|
Order string `url:"order,omitempty"`
|
||||||
|
LastKey string `url:"lastKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertRecipientsRequest provides necessary parameter structure to Retrieve recipients of an alert from OpsGenie.
|
||||||
|
type ListAlertRecipientsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Alias string `url:"alias,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertsRequest provides necessary parameter structure to Retrieve alerts from OpsGenie.
|
||||||
|
type ListAlertsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
CreatedAfter uint64 `url:"createdAfter,omitempty"`
|
||||||
|
CreatedBefore uint64 `url:"createdBefore,omitempty"`
|
||||||
|
UpdatedAfter uint64 `url:"updatedAfter,omitempty"`
|
||||||
|
UpdatedBefore uint64 `url:"updatedBefore,omitempty"`
|
||||||
|
Limit uint64 `url:"limit,omitempty"`
|
||||||
|
Status string `url:"status,omitempty"`
|
||||||
|
SortBy string `url:"sortBy,omitempty"`
|
||||||
|
Order string `url:"order,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountAlertRequest counts the alerts at OpsGenie.
|
||||||
|
type CountAlertRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
CreatedAfter uint64 `url:"createdAfter,omitempty"`
|
||||||
|
CreatedBefore uint64 `url:"createdBefore,omitempty"`
|
||||||
|
UpdatedAfter uint64 `url:"updatedAfter,omitempty"`
|
||||||
|
UpdatedBefore uint64 `url:"updatedBefore,omitempty"`
|
||||||
|
Limit uint64 `url:"limit,omitempty"`
|
||||||
|
Status string `url:"status,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
TagsOperator string `url:"tagsoperator,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenotifyAlertRequest provides necessary parameter structure to Re-notify recipients at OpsGenie.
|
||||||
|
type RenotifyAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Recipients []string `json:"recipients,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeOwnershipAlertRequest provides necessary parameter structure to Become the Owner of an alert at OpsGenie.
|
||||||
|
type TakeOwnershipAlertRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,365 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package client provides clients for using the OpsGenie Web API. Also prepares and sends requests.
|
||||||
|
//API user first creates a OpsGenieClient instance.
|
||||||
|
//
|
||||||
|
//cli := new(ogcli.OpsGenieClient)
|
||||||
|
//
|
||||||
|
//Following that he/she can set APIKey and some configurations for HTTP communication layer by setting
|
||||||
|
//a proxy definition and/or transport layer options.
|
||||||
|
//
|
||||||
|
//cli.SetAPIKey(constants.APIKey)
|
||||||
|
//
|
||||||
|
//Then create the client of the API type that he/she wants to use.
|
||||||
|
//
|
||||||
|
//alertCli, cliErr := cli.Alert()
|
||||||
|
//
|
||||||
|
//if cliErr != nil {
|
||||||
|
//panic(cliErr)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//The most fundamental and general use case is being able to access the
|
||||||
|
//OpsGenie Web API by coding a Go program.
|
||||||
|
//The program -by mean of a client application- can send OpsGenie Web API
|
||||||
|
//the requests using the 'client' package in a higher level. For the programmer
|
||||||
|
//of the client application, that reduces the number of LoCs.
|
||||||
|
//Besides it will result a less error-prone application and reduce
|
||||||
|
//the complexity by hiding the low-level networking, error-handling and
|
||||||
|
//byte-processing calls.
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/franela/goreq"
|
||||||
|
goquery "github.com/google/go-querystring/query"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// endpointURL is the base URL of OpsGenie Web API.
|
||||||
|
var endpointURL = "https://api.opsgenie.com"
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultConnectionTimeout time.Duration = 30 * time.Second
|
||||||
|
defaultRequestTimeout time.Duration = 60 * time.Second
|
||||||
|
defaultMaxRetryAttempts int = 5
|
||||||
|
timeSleepBetweenRequests time.Duration = 500 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestHeaderUserAgent contains User-Agent values tool/version (OS;GO_Version;language).
|
||||||
|
type requestHeaderUserAgent struct {
|
||||||
|
sdkName string
|
||||||
|
version string
|
||||||
|
os string
|
||||||
|
goVersion string
|
||||||
|
timezone string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString formats and returns RequestHeaderUserAgent type's fields as string.
|
||||||
|
func (p requestHeaderUserAgent) ToString() string {
|
||||||
|
return fmt.Sprintf("%s/%s (%s;%s;%s)", p.sdkName, p.version, p.os, p.goVersion, p.timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAgentParam requestHeaderUserAgent
|
||||||
|
|
||||||
|
/*
|
||||||
|
OpsGenieClient is a general data type used for:
|
||||||
|
- authenticating callers through their API keys and
|
||||||
|
- instantiating "alert", "heartbeat", "integration" and "policy" clients
|
||||||
|
- setting HTTP transport layer configurations
|
||||||
|
- setting Proxy configurations
|
||||||
|
*/
|
||||||
|
type OpsGenieClient struct {
|
||||||
|
proxy *ProxyConfiguration
|
||||||
|
httpTransportSettings *HTTPTransportSettings
|
||||||
|
apiKey string
|
||||||
|
opsGenieAPIURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProxyConfiguration sets proxy configurations of the OpsGenieClient.
|
||||||
|
func (cli *OpsGenieClient) SetProxyConfiguration(conf *ProxyConfiguration) {
|
||||||
|
cli.proxy = conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPTransportSettings sets HTTP transport layer configurations of the OpsGenieClient.
|
||||||
|
func (cli *OpsGenieClient) SetHTTPTransportSettings(settings *HTTPTransportSettings) {
|
||||||
|
cli.httpTransportSettings = settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAPIKey sets API Key of the OpsGenieClient and authenticates callers through the API Key at OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) SetAPIKey(key string) {
|
||||||
|
cli.apiKey = key
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieAPIUrl sets the endpoint(base URL) that requests will send. It can be used for testing purpose.
|
||||||
|
func (cli *OpsGenieClient) SetOpsGenieAPIUrl(url string) {
|
||||||
|
if url != "" {
|
||||||
|
cli.opsGenieAPIURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpsGenieAPIUrl returns the current endpoint(base URL) that requests will send.
|
||||||
|
func (cli *OpsGenieClient) OpsGenieAPIUrl() string {
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
cli.opsGenieAPIURL = endpointURL
|
||||||
|
}
|
||||||
|
return cli.opsGenieAPIURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIKey returns the API Key value that OpsGenieClient uses to authenticate at OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) APIKey() string {
|
||||||
|
return cli.apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeHTTPTransportSettings internal method to set default values of HTTP transport layer configuration if necessary.
|
||||||
|
func (cli *OpsGenieClient) makeHTTPTransportSettings() {
|
||||||
|
if cli.httpTransportSettings != nil {
|
||||||
|
if cli.httpTransportSettings.MaxRetryAttempts <= 0 {
|
||||||
|
cli.httpTransportSettings.MaxRetryAttempts = defaultMaxRetryAttempts
|
||||||
|
}
|
||||||
|
if cli.httpTransportSettings.ConnectionTimeout <= 0 {
|
||||||
|
cli.httpTransportSettings.ConnectionTimeout = defaultConnectionTimeout
|
||||||
|
}
|
||||||
|
if cli.httpTransportSettings.RequestTimeout <= 0 {
|
||||||
|
cli.httpTransportSettings.RequestTimeout = defaultRequestTimeout
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cli.httpTransportSettings = &HTTPTransportSettings{MaxRetryAttempts: defaultMaxRetryAttempts, ConnectionTimeout: defaultConnectionTimeout, RequestTimeout: defaultRequestTimeout}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert instantiates a new OpsGenieAlertClient.
|
||||||
|
func (cli *OpsGenieClient) Alert() (*OpsGenieAlertClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
alertClient := new(OpsGenieAlertClient)
|
||||||
|
alertClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
alertClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return alertClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat instantiates a new OpsGenieHeartbeatClient.
|
||||||
|
func (cli *OpsGenieClient) Heartbeat() (*OpsGenieHeartbeatClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
heartbeatClient := new(OpsGenieHeartbeatClient)
|
||||||
|
heartbeatClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
heartbeatClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return heartbeatClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integration instantiates a new OpsGenieIntegrationClient.
|
||||||
|
func (cli *OpsGenieClient) Integration() (*OpsGenieIntegrationClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
integrationClient := new(OpsGenieIntegrationClient)
|
||||||
|
integrationClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
integrationClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return integrationClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy instantiates a new OpsGeniePolicyClient.
|
||||||
|
func (cli *OpsGenieClient) Policy() (*OpsGeniePolicyClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
policyClient := new(OpsGeniePolicyClient)
|
||||||
|
policyClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
policyClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return policyClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team instantiates a new OpsGenieTeamClient.
|
||||||
|
func (cli *OpsGenieClient) Team() (*OpsGenieTeamClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
teamClient := new(OpsGenieTeamClient)
|
||||||
|
teamClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
teamClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return teamClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escalation instantiates a new OpsGenieEscalationClient.
|
||||||
|
func (cli *OpsGenieClient) Escalation() (*OpsGenieEscalationClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
escalationClient := new(OpsGenieEscalationClient)
|
||||||
|
escalationClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
escalationClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return escalationClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule instantiates a new OpsGenieScheduleClient.
|
||||||
|
func (cli *OpsGenieClient) Schedule() (*OpsGenieScheduleClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
scheduleClient := new(OpsGenieScheduleClient)
|
||||||
|
scheduleClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
scheduleClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduleClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// User instantiates a new OpsGenieUserClient.
|
||||||
|
func (cli *OpsGenieClient) User() (*OpsGenieUserClient, error) {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
|
||||||
|
userClient := new(OpsGenieUserClient)
|
||||||
|
userClient.SetOpsGenieClient(*cli)
|
||||||
|
|
||||||
|
if cli.opsGenieAPIURL == "" {
|
||||||
|
userClient.SetOpsGenieAPIUrl(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCommonRequestProps is an internal method to set common properties of requests that will send to OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) buildCommonRequestProps() goreq.Request {
|
||||||
|
if cli.httpTransportSettings == nil {
|
||||||
|
cli.makeHTTPTransportSettings()
|
||||||
|
}
|
||||||
|
goreq.SetConnectTimeout(cli.httpTransportSettings.ConnectionTimeout)
|
||||||
|
req := goreq.Request{}
|
||||||
|
if cli.proxy != nil {
|
||||||
|
req.Proxy = cli.proxy.toString()
|
||||||
|
}
|
||||||
|
req.UserAgent = userAgentParam.ToString()
|
||||||
|
req.Timeout = cli.httpTransportSettings.RequestTimeout
|
||||||
|
req.Insecure = true
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildGetRequest is an internal method to prepare a "GET" request that will send to OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) buildGetRequest(uri string, request interface{}) goreq.Request {
|
||||||
|
req := cli.buildCommonRequestProps()
|
||||||
|
req.Method = "GET"
|
||||||
|
req.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
uri = cli.OpsGenieAPIUrl() + uri
|
||||||
|
if request != nil {
|
||||||
|
v, _ := goquery.Values(request)
|
||||||
|
req.Uri = uri + "?" + v.Encode()
|
||||||
|
} else {
|
||||||
|
req.Uri = uri
|
||||||
|
}
|
||||||
|
logging.Logger().Info("Executing OpsGenie request to ["+uri+"] with parameters: ")
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPostRequest is an internal method to prepare a "POST" request that will send to OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) buildPostRequest(uri string, request interface{}) goreq.Request {
|
||||||
|
req := cli.buildCommonRequestProps()
|
||||||
|
req.Method = "POST"
|
||||||
|
req.ContentType = "application/json; charset=utf-8"
|
||||||
|
req.Uri = cli.OpsGenieAPIUrl() + uri
|
||||||
|
req.Body = request
|
||||||
|
j, _ := json.Marshal(request)
|
||||||
|
logging.Logger().Info("Executing OpsGenie request to ["+req.Uri+"] with content parameters: ", string(j))
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDeleteRequest is an internal method to prepare a "DELETE" request that will send to OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) buildDeleteRequest(uri string, request interface{}) goreq.Request {
|
||||||
|
req := cli.buildGetRequest(uri, request)
|
||||||
|
req.Method = "DELETE"
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest is an internal method to send the prepared requests to OpsGenie.
|
||||||
|
func (cli *OpsGenieClient) sendRequest(req goreq.Request) (*goreq.Response, error) {
|
||||||
|
// send the request
|
||||||
|
var resp *goreq.Response
|
||||||
|
var err error
|
||||||
|
for i := 0; i < cli.httpTransportSettings.MaxRetryAttempts; i++ {
|
||||||
|
resp, err = req.Do()
|
||||||
|
if err == nil && resp.StatusCode < 500 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
logging.Logger().Info(fmt.Sprintf("Retrying request [%s] ResponseCode:[%d]. RetryCount: %d", req.Uri, resp.StatusCode, (i + 1)))
|
||||||
|
} else {
|
||||||
|
logging.Logger().Info(fmt.Sprintf("Retrying request [%s] Reason:[%s]. RetryCount: %d", req.Uri, err.Error(), (i + 1)))
|
||||||
|
}
|
||||||
|
time.Sleep(timeSleepBetweenRequests * time.Duration(i+1))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
message := "Unable to send the request " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
// check for the returning http status
|
||||||
|
statusCode := resp.StatusCode
|
||||||
|
if statusCode >= 400 {
|
||||||
|
body, err := resp.Body.ToString()
|
||||||
|
if err != nil {
|
||||||
|
message := "Server response with error can not be parsed " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return nil, errorMessage(statusCode, body)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorMessage is an internal method to return formatted error message according to HTTP status code of the response.
|
||||||
|
func errorMessage(httpStatusCode int, responseBody string) error {
|
||||||
|
if httpStatusCode >= 400 && httpStatusCode < 500 {
|
||||||
|
message := fmt.Sprintf("Client error occurred; Response Code: %d, Response Body: %s", httpStatusCode, responseBody)
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return errors.New(message)
|
||||||
|
}
|
||||||
|
if httpStatusCode >= 500 {
|
||||||
|
message := fmt.Sprintf("Server error occurred; Response Code: %d, Response Body: %s", httpStatusCode, responseBody)
|
||||||
|
logging.Logger().Info(message)
|
||||||
|
return errors.New(message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializer for the package client
|
||||||
|
// Initializes the User-Agent parameter of the requests.
|
||||||
|
// TODO version information must be read from a MANIFEST file
|
||||||
|
func init() {
|
||||||
|
userAgentParam.sdkName = "opsgenie-go-sdk"
|
||||||
|
userAgentParam.version = "1.0.0"
|
||||||
|
userAgentParam.os = runtime.GOOS
|
||||||
|
userAgentParam.goVersion = runtime.Version()
|
||||||
|
userAgentParam.timezone = time.Local.String()
|
||||||
|
}
|
37
vendor/github.com/opsgenie/opsgenie-go-sdk/client/client_configuration.go
generated
vendored
Normal file
37
vendor/github.com/opsgenie/opsgenie-go-sdk/client/client_configuration.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProxyConfiguration is the type that contains the proxy configurations of the OpsGenieClient.
|
||||||
|
type ProxyConfiguration struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
ProxyURI string
|
||||||
|
Protocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPTransportSettings is the type that contains the HTTP transport layer configurations of the OpsGenieClient.
|
||||||
|
type HTTPTransportSettings struct {
|
||||||
|
ConnectionTimeout time.Duration
|
||||||
|
RequestTimeout time.Duration
|
||||||
|
MaxRetryAttempts int
|
||||||
|
}
|
||||||
|
|
||||||
|
// toString is an internal method that formats and returns proxy configurations of the OpsGenieClient.
|
||||||
|
func (proxy *ProxyConfiguration) toString() string {
|
||||||
|
if proxy.ProxyURI != "" {
|
||||||
|
return proxy.ProxyURI
|
||||||
|
}
|
||||||
|
if proxy.Protocol == "" {
|
||||||
|
proxy.Protocol = "http"
|
||||||
|
}
|
||||||
|
if proxy.Username != "" && proxy.Password != "" {
|
||||||
|
return fmt.Sprintf("%s://%s:%s@%s:%d", proxy.Protocol, proxy.Username, proxy.Password, proxy.Host, proxy.Port)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s://%s:%d", proxy.Protocol, proxy.Host, proxy.Port)
|
||||||
|
}
|
559
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_alert_client.go
generated
vendored
Normal file
559
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_alert_client.go
generated
vendored
Normal file
|
@ -0,0 +1,559 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/alerts"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
createAlertURL = "/v1/json/alert"
|
||||||
|
closeAlertURL = "/v1/json/alert/close"
|
||||||
|
deleteAlertURL = "/v1/json/alert"
|
||||||
|
getAlertURL = "/v1/json/alert"
|
||||||
|
listAlertsURL = "/v1/json/alert"
|
||||||
|
listAlertNotesURL = "/v1/json/alert/note"
|
||||||
|
listAlertLogsURL = "/v1/json/alert/log"
|
||||||
|
listAlertRecipientsURL = "/v1/json/alert/recipient"
|
||||||
|
acknowledgeAlertURL = "/v1/json/alert/acknowledge"
|
||||||
|
renotifyAlertURL = "/v1/json/alert/renotify"
|
||||||
|
takeOwnershipAlertURL = "/v1/json/alert/takeOwnership"
|
||||||
|
assignOwnershipAlertURL = "/v1/json/alert/assign"
|
||||||
|
addTeamAlertURL = "/v1/json/alert/team"
|
||||||
|
addRecipientAlertURL = "/v1/json/alert/recipient"
|
||||||
|
addNoteAlertURL = "/v1/json/alert/note"
|
||||||
|
addTagsAlertURL = "/v1/json/alert/tags"
|
||||||
|
executeActionAlertURL = "/v1/json/alert/executeAction"
|
||||||
|
attachFileAlertURL = "/v1/json/alert/attach"
|
||||||
|
countAlertURL = "/v1/json/alert/count"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieAlertClient is the data type to make Alert API requests.
|
||||||
|
type OpsGenieAlertClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieAlertClient.
|
||||||
|
func (cli *OpsGenieAlertClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create method creates an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Create(req alerts.CreateAlertRequest) (*alerts.CreateAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(createAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var createAlertResp alerts.CreateAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&createAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &createAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count method counts alerts at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Count(req alerts.CountAlertRequest) (*alerts.CountAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(countAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var countAlertResp alerts.CountAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&countAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &countAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Close method closes an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Close(req alerts.CloseAlertRequest) (*alerts.CloseAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(closeAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var closeAlertResp alerts.CloseAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&closeAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &closeAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Delete(req alerts.DeleteAlertRequest) (*alerts.DeleteAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(deleteAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteAlertResp alerts.DeleteAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &deleteAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves specified alert details from OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Get(req alerts.GetAlertRequest) (*alerts.GetAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(getAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var getAlertResp alerts.GetAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&getAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &getAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves alerts from OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) List(req alerts.ListAlertsRequest) (*alerts.ListAlertsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(listAlertsURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listAlertsResp alerts.ListAlertsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listAlertsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listAlertsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNotes method retrieves notes of an alert from OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) ListNotes(req alerts.ListAlertNotesRequest) (*alerts.ListAlertNotesResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(listAlertNotesURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listAlertNotesResp alerts.ListAlertNotesResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listAlertNotesResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listAlertNotesResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogs method retrieves activity logs of an alert from OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) ListLogs(req alerts.ListAlertLogsRequest) (*alerts.ListAlertLogsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(listAlertLogsURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listAlertLogsResp alerts.ListAlertLogsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listAlertLogsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listAlertLogsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRecipients method retrieves recipients of an alert from OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) ListRecipients(req alerts.ListAlertRecipientsRequest) (*alerts.ListAlertRecipientsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(listAlertRecipientsURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listAlertRecipientsResp alerts.ListAlertRecipientsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listAlertRecipientsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listAlertRecipientsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge method acknowledges an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Acknowledge(req alerts.AcknowledgeAlertRequest) (*alerts.AcknowledgeAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(acknowledgeAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var acknowledgeAlertResp alerts.AcknowledgeAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&acknowledgeAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &acknowledgeAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renotify re-notifies recipients at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) Renotify(req alerts.RenotifyAlertRequest) (*alerts.RenotifyAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(renotifyAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var renotifyAlertResp alerts.RenotifyAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&renotifyAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &renotifyAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeOwnership method takes the ownership of an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) TakeOwnership(req alerts.TakeOwnershipAlertRequest) (*alerts.TakeOwnershipAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(takeOwnershipAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var takeOwnershipResp alerts.TakeOwnershipAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&takeOwnershipResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &takeOwnershipResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignOwner method assigns the specified user as the owner of the alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AssignOwner(req alerts.AssignOwnerAlertRequest) (*alerts.AssignOwnerAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(assignOwnershipAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var assignOwnerAlertResp alerts.AssignOwnerAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&assignOwnerAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &assignOwnerAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeam method adds a team to an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AddTeam(req alerts.AddTeamAlertRequest) (*alerts.AddTeamAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(addTeamAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var addTeamAlertResp alerts.AddTeamAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&addTeamAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &addTeamAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRecipient method adds recipient to an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AddRecipient(req alerts.AddRecipientAlertRequest) (*alerts.AddRecipientAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(addRecipientAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var addRecipientAlertResp alerts.AddRecipientAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&addRecipientAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &addRecipientAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNote method adds a note to an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AddNote(req alerts.AddNoteAlertRequest) (*alerts.AddNoteAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(addNoteAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var addNoteAlertResp alerts.AddNoteAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&addNoteAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &addNoteAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTags method adds tags to an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AddTags(req alerts.AddTagsAlertRequest) (*alerts.AddTagsAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(addTagsAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var addTagsAlertResp alerts.AddTagsAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&addTagsAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &addTagsAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteAction method executes a custom action on an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) ExecuteAction(req alerts.ExecuteActionAlertRequest) (*alerts.ExecuteActionAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(executeActionAlertURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var executeActionAlertResp alerts.ExecuteActionAlertResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&executeActionAlertResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &executeActionAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFile method attaches a file to an alert at OpsGenie.
|
||||||
|
func (cli *OpsGenieAlertClient) AttachFile(req alerts.AttachFileAlertRequest) (*alerts.AttachFileAlertResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
|
||||||
|
path := req.Attachment.Name()
|
||||||
|
file, err := os.Open(path)
|
||||||
|
defer file.Close()
|
||||||
|
if err != nil {
|
||||||
|
message := "Attachment can not be opened for reading. " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
// add the attachment
|
||||||
|
fw, err := w.CreateFormFile("attachment", filepath.Base(path))
|
||||||
|
if err != nil {
|
||||||
|
message := "Can not build the request with the field attachment. " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(fw, file); err != nil {
|
||||||
|
message := "Can not copy the attachment into the request. " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the other fields
|
||||||
|
// empty fields should not be placed into the request
|
||||||
|
// otherwise it yields an incomplete boundary exception
|
||||||
|
if req.APIKey != "" {
|
||||||
|
if err = writeField(*w, "apiKey", req.APIKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.ID != "" {
|
||||||
|
if err = writeField(*w, "id", req.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Alias != "" {
|
||||||
|
if err = writeField(*w, "alias", req.Alias); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.User != "" {
|
||||||
|
if err = writeField(*w, "user", req.User); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Source != "" {
|
||||||
|
if err = writeField(*w, "source", req.Source); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.IndexFile != "" {
|
||||||
|
if err = writeField(*w, "indexFile", req.IndexFile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Note != "" {
|
||||||
|
if err = writeField(*w, "note", req.Note); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
httpReq, err := http.NewRequest("POST", cli.opsGenieAPIURL+attachFileAlertURL, &b)
|
||||||
|
if err != nil {
|
||||||
|
message := "Can not create the multipart/form-data request. " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
httpReq.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: func(netw, addr string) (net.Conn, error) {
|
||||||
|
conn, err := net.DialTimeout(netw, addr, cli.httpTransportSettings.ConnectionTimeout)
|
||||||
|
if err != nil {
|
||||||
|
message := "Error occurred while connecting: " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(cli.httpTransportSettings.RequestTimeout))
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: transport}
|
||||||
|
// proxy settings
|
||||||
|
if cli.proxy != nil {
|
||||||
|
proxyURL, proxyErr := url.Parse(cli.proxy.toString())
|
||||||
|
if proxyErr != nil {
|
||||||
|
message := "Can not set the proxy configuration " + proxyErr.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
transport.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
}
|
||||||
|
url := httpReq.URL.String()
|
||||||
|
logging.Logger().Info("Executing OpsGenie request to [" + url + "] with multipart data.")
|
||||||
|
var res *http.Response
|
||||||
|
for i := 0; i < cli.httpTransportSettings.MaxRetryAttempts; i++ {
|
||||||
|
res, err = client.Do(httpReq)
|
||||||
|
if err == nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
logging.Logger().Info(fmt.Sprintf("Retrying request [%s] ResponseCode:[%d]. RetryCount: %d", url, res.StatusCode, (i + 1)))
|
||||||
|
} else {
|
||||||
|
logging.Logger().Info(fmt.Sprintf("Retrying request [%s] Reason:[%s]. RetryCount: %d", url, err.Error(), (i + 1)))
|
||||||
|
}
|
||||||
|
time.Sleep(timeSleepBetweenRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
message := "Can not attach the file, unable to send the request. " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpStatusCode := res.StatusCode
|
||||||
|
if httpStatusCode >= 400 {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err == nil {
|
||||||
|
return nil, errorMessage(httpStatusCode, string(body[:]))
|
||||||
|
}
|
||||||
|
message := fmt.Sprint("Couldn't read the response, %s", err.Error())
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachFileAlertResp := alerts.AttachFileAlertResponse{Status: res.Status, Code: res.StatusCode}
|
||||||
|
return &attachFileAlertResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeField(w multipart.Writer, fieldName string, fieldVal string) error {
|
||||||
|
if err := w.WriteField(fieldName, fieldVal); err != nil {
|
||||||
|
message := "Can not write field " + fieldName + " into the request. Reason: " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return errors.New(message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
121
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_escalation_client.go
generated
vendored
Normal file
121
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_escalation_client.go
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/escalation"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
escalationURL = "/v1/json/escalation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieEscalationClient is the data type to make Escalation API requests.
|
||||||
|
type OpsGenieEscalationClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieEscalationClient.
|
||||||
|
func (cli *OpsGenieEscalationClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create method creates a escalation at OpsGenie.
|
||||||
|
func (cli *OpsGenieEscalationClient) Create(req escalation.CreateEscalationRequest) (*escalation.CreateEscalationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(escalationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var createEscalationResp escalation.CreateEscalationResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&createEscalationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &createEscalationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update method updates a escalation at OpsGenie.
|
||||||
|
func (cli *OpsGenieEscalationClient) Update(req escalation.UpdateEscalationRequest) (*escalation.UpdateEscalationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(escalationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var updateEscalationResp escalation.UpdateEscalationResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&updateEscalationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &updateEscalationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes a escalation at OpsGenie.
|
||||||
|
func (cli *OpsGenieEscalationClient) Delete(req escalation.DeleteEscalationRequest) (*escalation.DeleteEscalationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(escalationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteEscalationResp escalation.DeleteEscalationResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteEscalationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &deleteEscalationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves specified escalation details from OpsGenie.
|
||||||
|
func (cli *OpsGenieEscalationClient) Get(req escalation.GetEscalationRequest) (*escalation.GetEscalationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(escalationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var getEscalationResp escalation.GetEscalationResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&getEscalationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &getEscalationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves escalations from OpsGenie.
|
||||||
|
func (cli *OpsGenieEscalationClient) List(req escalation.ListEscalationsRequest) (*escalation.ListEscalationsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(escalationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listEscalationsResp escalation.ListEscalationsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listEscalationsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listEscalationsResp, nil
|
||||||
|
}
|
191
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_heartbeat_client.go
generated
vendored
Normal file
191
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_heartbeat_client.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/heartbeat"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
addHeartbeatURL = "/v1/json/heartbeat"
|
||||||
|
updateHeartbeatURL = "/v1/json/heartbeat"
|
||||||
|
enableHeartbeatURL = "/v1/json/heartbeat/enable"
|
||||||
|
disableHeartbeatURL = "/v1/json/heartbeat/disable"
|
||||||
|
deleteHeartbeatURL = "/v1/json/heartbeat"
|
||||||
|
getHeartbeatURL = "/v1/json/heartbeat"
|
||||||
|
listHeartbeatURL = "/v1/json/heartbeat"
|
||||||
|
sendHeartbeatURL = "/v1/json/heartbeat/send"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieHeartbeatClient is the data type to make Heartbeat API requests.
|
||||||
|
type OpsGenieHeartbeatClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieHeartbeatClient.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method creates a heartbeat at OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Add(req heartbeat.AddHeartbeatRequest) (*heartbeat.AddHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(addHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var addHeartbeatResp heartbeat.AddHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&addHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &addHeartbeatResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update method changes configuration of an existing heartbeat at OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Update(req heartbeat.UpdateHeartbeatRequest) (*heartbeat.UpdateHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(updateHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var updateHeartbeatResp heartbeat.UpdateHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&updateHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &updateHeartbeatResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable method enables an heartbeat at OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Enable(req heartbeat.EnableHeartbeatRequest) (*heartbeat.EnableHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(enableHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var enableHeartbeatResp heartbeat.EnableHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&enableHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &enableHeartbeatResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable method disables an heartbeat at OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Disable(req heartbeat.DisableHeartbeatRequest) (*heartbeat.DisableHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(disableHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var disableHeartbeatResp heartbeat.DisableHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&disableHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &disableHeartbeatResp, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes an heartbeat from OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Delete(req heartbeat.DeleteHeartbeatRequest) (*heartbeat.DeleteHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(deleteHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteHeartbeatResp heartbeat.DeleteHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deleteHeartbeatResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves an heartbeat with details from OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Get(req heartbeat.GetHeartbeatRequest) (*heartbeat.GetHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(getHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var getHeartbeatResp heartbeat.GetHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&getHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &getHeartbeatResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves heartbeats from OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) List(req heartbeat.ListHeartbeatsRequest) (*heartbeat.ListHeartbeatsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(listHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listHeartbeatsResp heartbeat.ListHeartbeatsResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&listHeartbeatsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listHeartbeatsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send method sends an Heartbeat Signal to OpsGenie.
|
||||||
|
func (cli *OpsGenieHeartbeatClient) Send(req heartbeat.SendHeartbeatRequest) (*heartbeat.SendHeartbeatResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(sendHeartbeatURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var sendHeartbeatResp heartbeat.SendHeartbeatResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&sendHeartbeatResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sendHeartbeatResp, nil
|
||||||
|
}
|
63
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_integration_client.go
generated
vendored
Normal file
63
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_integration_client.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
integration "github.com/opsgenie/opsgenie-go-sdk/integration"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enableIntegrationURL = "/v1/json/integration/enable"
|
||||||
|
disableIntegrationURL = "/v1/json/integration/disable"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieIntegrationClient is the data type to make Integration API requests.
|
||||||
|
type OpsGenieIntegrationClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieIntegrationClient.
|
||||||
|
func (cli *OpsGenieIntegrationClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable method enables an Integration at OpsGenie.
|
||||||
|
func (cli *OpsGenieIntegrationClient) Enable(req integration.EnableIntegrationRequest) (*integration.EnableIntegrationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(enableIntegrationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var enableIntegrationResp integration.EnableIntegrationResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&enableIntegrationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &enableIntegrationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable method disables an Integration at OpsGenie.
|
||||||
|
func (cli *OpsGenieIntegrationClient) Disable(req integration.DisableIntegrationRequest) (*integration.DisableIntegrationResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(disableIntegrationURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var disableIntegrationResp integration.DisableIntegrationResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&disableIntegrationResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &disableIntegrationResp, nil
|
||||||
|
}
|
61
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_policy_client.go
generated
vendored
Normal file
61
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_policy_client.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
policy "github.com/opsgenie/opsgenie-go-sdk/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enablePolicyURL = "/v1/json/alert/policy/enable"
|
||||||
|
disablePolicyURL = "/v1/json/alert/policy/disable"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGeniePolicyClient is the data type to make Policy API requests.
|
||||||
|
type OpsGeniePolicyClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGeniePolicyClient.
|
||||||
|
func (cli *OpsGeniePolicyClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable method enables an Policy at OpsGenie.
|
||||||
|
func (cli *OpsGeniePolicyClient) Enable(req policy.EnablePolicyRequest) (*policy.EnablePolicyResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(enablePolicyURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var enablePolicyResp policy.EnablePolicyResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&enablePolicyResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &enablePolicyResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable method disables an Policy at OpsGenie.
|
||||||
|
func (cli *OpsGeniePolicyClient) Disable(req policy.DisablePolicyRequest) (*policy.DisablePolicyResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(disablePolicyURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var disablePolicyResp policy.DisablePolicyResponse
|
||||||
|
if err = resp.Body.FromJsonTo(&disablePolicyResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &disablePolicyResp, nil
|
||||||
|
}
|
124
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_schedule_client.go
generated
vendored
Normal file
124
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_schedule_client.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/schedule"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
scheduleURL = "/v1/json/schedule"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieScheduleClient is the data type to make Schedule API requests.
|
||||||
|
type OpsGenieScheduleClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieScheduleClient.
|
||||||
|
func (cli *OpsGenieScheduleClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create method creates a schedule at OpsGenie.
|
||||||
|
func (cli *OpsGenieScheduleClient) Create(req schedule.CreateScheduleRequest) (*schedule.CreateScheduleResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(scheduleURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var createScheduleResp schedule.CreateScheduleResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&createScheduleResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &createScheduleResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update method updates a schedule at OpsGenie.
|
||||||
|
func (cli *OpsGenieScheduleClient) Update(req schedule.UpdateScheduleRequest) (*schedule.UpdateScheduleResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(scheduleURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var updateScheduleResp schedule.UpdateScheduleResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&updateScheduleResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &updateScheduleResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes a schedule at OpsGenie.
|
||||||
|
func (cli *OpsGenieScheduleClient) Delete(req schedule.DeleteScheduleRequest) (*schedule.DeleteScheduleResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(scheduleURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteScheduleResp schedule.DeleteScheduleResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteScheduleResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &deleteScheduleResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves specified schedule details from OpsGenie.
|
||||||
|
func (cli *OpsGenieScheduleClient) Get(req schedule.GetScheduleRequest) (*schedule.GetScheduleResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(scheduleURL, req))
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var getScheduleResp schedule.GetScheduleResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&getScheduleResp); err != nil {
|
||||||
|
fmt.Println("Error parsing json")
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v", getScheduleResp)
|
||||||
|
return &getScheduleResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves schedules from OpsGenie.
|
||||||
|
func (cli *OpsGenieScheduleClient) List(req schedule.ListSchedulesRequest) (*schedule.ListSchedulesResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(scheduleURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listSchedulesResp schedule.ListSchedulesResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listSchedulesResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listSchedulesResp, nil
|
||||||
|
}
|
142
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_team_client.go
generated
vendored
Normal file
142
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_team_client.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/team"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
teamURL = "/v1/json/team"
|
||||||
|
teamLogsURL = "/v1/json/team/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieTeamClient is the data type to make Team API requests.
|
||||||
|
type OpsGenieTeamClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieTeamClient.
|
||||||
|
func (cli *OpsGenieTeamClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create method creates a team at OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) Create(req team.CreateTeamRequest) (*team.CreateTeamResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(teamURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var createTeamResp team.CreateTeamResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&createTeamResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &createTeamResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update method updates a team at OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) Update(req team.UpdateTeamRequest) (*team.UpdateTeamResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(teamURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var updateTeamResp team.UpdateTeamResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&updateTeamResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &updateTeamResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes a team at OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) Delete(req team.DeleteTeamRequest) (*team.DeleteTeamResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(teamURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteTeamResp team.DeleteTeamResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteTeamResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &deleteTeamResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves specified team details from OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) Get(req team.GetTeamRequest) (*team.GetTeamResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(teamURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var getTeamResp team.GetTeamResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&getTeamResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &getTeamResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves teams from OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) List(req team.ListTeamsRequest) (*team.ListTeamsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(teamURL,req))
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listTeamsResp team.ListTeamsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listTeamsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listTeamsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogs method retrieves team logs from OpsGenie.
|
||||||
|
func (cli *OpsGenieTeamClient) ListLogs(req team.ListTeamLogsRequest) (*team.ListTeamLogsResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(teamLogsURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listTeamLogsResp team.ListTeamLogsResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listTeamLogsResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listTeamLogsResp, nil
|
||||||
|
}
|
122
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_user_client.go
generated
vendored
Normal file
122
vendor/github.com/opsgenie/opsgenie-go-sdk/client/opsgenie_user_client.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/user"
|
||||||
|
"github.com/opsgenie/opsgenie-go-sdk/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userURL = "/v1/json/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpsGenieUserClient is the data type to make User API requests.
|
||||||
|
type OpsGenieUserClient struct {
|
||||||
|
OpsGenieClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpsGenieClient sets the embedded OpsGenieClient type of the OpsGenieUserClient.
|
||||||
|
func (cli *OpsGenieUserClient) SetOpsGenieClient(ogCli OpsGenieClient) {
|
||||||
|
cli.OpsGenieClient = ogCli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create method creates a user at OpsGenie.
|
||||||
|
func (cli *OpsGenieUserClient) Create(req user.CreateUserRequest) (*user.CreateUserResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(userURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var createUserResp user.CreateUserResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&createUserResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &createUserResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update method updates a user at OpsGenie.
|
||||||
|
func (cli *OpsGenieUserClient) Update(req user.UpdateUserRequest) (*user.UpdateUserResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildPostRequest(userURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var updateUserResp user.UpdateUserResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&updateUserResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &updateUserResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete method deletes a user at OpsGenie.
|
||||||
|
func (cli *OpsGenieUserClient) Delete(req user.DeleteUserRequest) (*user.DeleteUserResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildDeleteRequest(userURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var deleteUserResp user.DeleteUserResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&deleteUserResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &deleteUserResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get method retrieves specified user details from OpsGenie.
|
||||||
|
func (cli *OpsGenieUserClient) Get(req user.GetUserRequest) (*user.GetUserResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(userURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var getUserResp user.GetUserResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&getUserResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &getUserResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List method retrieves users from OpsGenie.
|
||||||
|
func (cli *OpsGenieUserClient) List(req user.ListUsersRequest) (*user.ListUsersResponse, error) {
|
||||||
|
req.APIKey = cli.apiKey
|
||||||
|
resp, err := cli.sendRequest(cli.buildGetRequest(userURL, req))
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var listUsersResp user.ListUsersResponse
|
||||||
|
|
||||||
|
if err = resp.Body.FromJsonTo(&listUsersResp); err != nil {
|
||||||
|
message := "Server response can not be parsed, " + err.Error()
|
||||||
|
logging.Logger().Warn(message)
|
||||||
|
return nil, errors.New(message)
|
||||||
|
}
|
||||||
|
return &listUsersResp, nil
|
||||||
|
}
|
50
vendor/github.com/opsgenie/opsgenie-go-sdk/escalation/escalation.go
generated
vendored
Normal file
50
vendor/github.com/opsgenie/opsgenie-go-sdk/escalation/escalation.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package escalation provides requests and response structures to achieve Escalation API actions.
|
||||||
|
package escalation
|
||||||
|
|
||||||
|
// Rule defines the structure for each escalation rule definition
|
||||||
|
type Rule struct {
|
||||||
|
Delay int `json:"delay"`
|
||||||
|
Notify string `json:"notify,omitempty"`
|
||||||
|
NotifyType string `json:"notifyType,omitempty"`
|
||||||
|
NotifyCondition string `json:"notifyCondition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEscalationRequest provides necessary parameter structure for creating escalation
|
||||||
|
type CreateEscalationRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEscalationRequest provides necessary parameter structure for updating an escalation
|
||||||
|
type UpdateEscalationRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEscalationRequest provides necessary parameter structure for deleting an escalation
|
||||||
|
type DeleteEscalationRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
ID string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEscalationRequest provides necessary parameter structure for requesting escalation information
|
||||||
|
type GetEscalationRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEscalationRequest provides necessary parameter structure for listing escalations
|
||||||
|
type ListEscalationsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
}
|
34
vendor/github.com/opsgenie/opsgenie-go-sdk/escalation/escalation_responses.go
generated
vendored
Normal file
34
vendor/github.com/opsgenie/opsgenie-go-sdk/escalation/escalation_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package escalation
|
||||||
|
|
||||||
|
// Create escalation response structure
|
||||||
|
type CreateEscalationResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update escalation response structure
|
||||||
|
type UpdateEscalationResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete escalation response structure
|
||||||
|
type DeleteEscalationResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get escalation structure
|
||||||
|
type GetEscalationResponse struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Team string `json:"team,omitempty"`
|
||||||
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// List escalations response structure
|
||||||
|
type ListEscalationsResponse struct {
|
||||||
|
Escalations []GetEscalationResponse `json:"escalations,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package heartbeat provides requests and response structures to achieve Heartbeat API actions.
|
||||||
|
package heartbeat
|
||||||
|
|
||||||
|
// AddHeartbeatRequest provides necessary parameter structure to Create an Heartbeat at OpsGenie.
|
||||||
|
type AddHeartbeatRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Interval int `json:"interval,omitempty"`
|
||||||
|
IntervalUnit string `json:"intervalUnit,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeartbeatRequest provides necessary parameter structure to Update an existing Heartbeat at OpsGenie.
|
||||||
|
type UpdateHeartbeatRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Interval int `json:"interval,omitempty"`
|
||||||
|
IntervalUnit string `json:"intervalUnit,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableHeartbeatRequest provides necessary parameter structure to Enable an Heartbeat at OpsGenie.
|
||||||
|
type EnableHeartbeatRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableHeartbeatRequest provides necessary parameter structure to Disable an Heartbeat at OpsGenie.
|
||||||
|
type DisableHeartbeatRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHeartbeatRequest provides necessary parameter structure to Delete an Heartbeat from OpsGenie.
|
||||||
|
type DeleteHeartbeatRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatRequest provides necessary parameter structure to Retrieve an Heartbeat with details from OpsGenie.
|
||||||
|
type GetHeartbeatRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHeartbeatsRequest provides necessary parameter structure to Retrieve Heartbeats from OpsGenie.
|
||||||
|
type ListHeartbeatsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHeartbeatRequest provides necessary parameter structure to Send an Heartbeat Signal to OpsGenie.
|
||||||
|
type SendHeartbeatRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
63
vendor/github.com/opsgenie/opsgenie-go-sdk/heartbeat/heartbeat_responses.go
generated
vendored
Normal file
63
vendor/github.com/opsgenie/opsgenie-go-sdk/heartbeat/heartbeat_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package heartbeat
|
||||||
|
|
||||||
|
// AddHeartbeatResponse holds the result data of the AddHeartbeatRequest.
|
||||||
|
type AddHeartbeatResponse struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeartbeatResponse holds the result data of the UpdateHeartbeatRequest.
|
||||||
|
type UpdateHeartbeatResponse struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableHeartbeatResponse holds the result data of the EnableHeartbeatRequest.
|
||||||
|
type EnableHeartbeatResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableHeartbeatResponse holds the result data of the DisableHeartbeatRequest.
|
||||||
|
type DisableHeartbeatResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHeartbeatResponse holds the result data of the DeleteHeartbeatRequest.
|
||||||
|
type DeleteHeartbeatResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatResponse holds the result data of the GetHeartbeatRequest.
|
||||||
|
type GetHeartbeatResponse struct {
|
||||||
|
Heartbeat
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHeartbeatsResponse holds the result data of the ListHeartbeatsRequest.
|
||||||
|
type ListHeartbeatsResponse struct {
|
||||||
|
Heartbeats []Heartbeat `json:"heartbeats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Heartbeat struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
LastHeartbeat uint64 `json:"lastHeartBeat"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
IntervalUnit string `json:"intervalUnit"`
|
||||||
|
Expired bool `json:"expired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHeartbeatResponse holds the result data of the SendHeartbeatRequest.
|
||||||
|
type SendHeartbeatResponse struct {
|
||||||
|
WillExpireAt uint64 `json:"willExpireAt"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Heartbeat uint64 `json:"heartbeat"`
|
||||||
|
Took int `json:"took"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
22
vendor/github.com/opsgenie/opsgenie-go-sdk/integration/integration.go
generated
vendored
Normal file
22
vendor/github.com/opsgenie/opsgenie-go-sdk/integration/integration.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package integration provides requests and response structures to achieve Integration API actions.
|
||||||
|
package integration
|
||||||
|
|
||||||
|
// EnableIntegrationRequest provides necessary parameter structure to Enable an integration at OpsGenie.
|
||||||
|
type EnableIntegrationRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableIntegrationRequest provides necessary parameter structure to Disable an integration at OpsGenie.
|
||||||
|
type DisableIntegrationRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
13
vendor/github.com/opsgenie/opsgenie-go-sdk/integration/integration_responses.go
generated
vendored
Normal file
13
vendor/github.com/opsgenie/opsgenie-go-sdk/integration/integration_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
// EnableIntegrationResponse holds the result data of the EnableIntegrationRequest.
|
||||||
|
type EnableIntegrationResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableIntegrationResponse holds the result data of the DisableIntegrationRequest.
|
||||||
|
type DisableIntegrationResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package logging provides log interface.
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logger is the internal logger object.
|
||||||
|
var logger seelog.LoggerInterface
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output.
|
||||||
|
func DisableLog() {
|
||||||
|
logger = seelog.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger is a wrapper for Seelog's UseLogger function. It sets the newLogger as the current logger.
|
||||||
|
func UseLogger(newLogger seelog.LoggerInterface) {
|
||||||
|
logger = newLogger
|
||||||
|
seelog.UseLogger(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger returns internal logger object to achieve logging.
|
||||||
|
func Logger() seelog.LoggerInterface {
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureLogger configures the new logger according to the configuration and sets it as the current logger.
|
||||||
|
func ConfigureLogger(testConfig []byte) {
|
||||||
|
loggr, err := seelog.LoggerFromConfigAsBytes([]byte(testConfig))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error occured: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
UseLogger(loggr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushLog is a wrapper for seelog's Flush function. It flushes all the messages in the logger.
|
||||||
|
func FlushLog() {
|
||||||
|
logger.Flush()
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpsGenie. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package policy provides requests and response structures to achieve Policy API actions.
|
||||||
|
package policy
|
||||||
|
|
||||||
|
// EnablePolicyRequest provides necessary parameter structure to Enable a policy at OpsGenie.
|
||||||
|
type EnablePolicyRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisablePolicyRequest provides necessary parameter structure to Disable a policy at OpsGenie.
|
||||||
|
type DisablePolicyRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
13
vendor/github.com/opsgenie/opsgenie-go-sdk/policy/policy_responses.go
generated
vendored
Normal file
13
vendor/github.com/opsgenie/opsgenie-go-sdk/policy/policy_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
// EnablePolicyResponse holds the result data of the EnablePolicyRequest.
|
||||||
|
type EnablePolicyResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisablePolicyResponse holds the result data of the DisablePolicyRequest.
|
||||||
|
type DisablePolicyResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package schedule provides requests and response structures to achieve Schedule API actions.
|
||||||
|
package schedule
|
||||||
|
|
||||||
|
// Restrictions defines the structure for each rotation restrictions
|
||||||
|
type Restriction struct {
|
||||||
|
StartDay string `json:"startDay,omitempty"`
|
||||||
|
StartTime string `json:"startTime,omitempty"`
|
||||||
|
EndDay string `json:"endDay,omitempty"`
|
||||||
|
EndTime string `json:"endTime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotation defines the structure for each rotation definition
|
||||||
|
type Rotation struct {
|
||||||
|
StartDate string `json:"startDate,omitempty"`
|
||||||
|
EndDate string `json:"endDate,omitempty"`
|
||||||
|
RotationType string `json:"rotationType,omitempty"`
|
||||||
|
Participants []string `json:"participants,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
RotationLength int `json:"rotationLength,omitempty"`
|
||||||
|
Restrictions []Restriction `json:"restrictions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateScheduleRequest provides necessary parameter structure for creating Schedule
|
||||||
|
type CreateScheduleRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
Rotations []Rotation `json:"rotations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScheduleRequest provides necessary parameter structure for updating an Schedule
|
||||||
|
type UpdateScheduleRequest struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
Rotations []Rotation `json:"rotations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteScheduleRequest provides necessary parameter structure for deleting an Schedule
|
||||||
|
type DeleteScheduleRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScheduleRequest provides necessary parameter structure for requesting Schedule information
|
||||||
|
type GetScheduleRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListScheduleRequest provides necessary parameter structure for listing Schedules
|
||||||
|
type ListSchedulesRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
}
|
51
vendor/github.com/opsgenie/opsgenie-go-sdk/schedule/schedule_responses.go
generated
vendored
Normal file
51
vendor/github.com/opsgenie/opsgenie-go-sdk/schedule/schedule_responses.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package schedule
|
||||||
|
|
||||||
|
// Create schedule response structure
|
||||||
|
type CreateScheduleResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update schedule response structure
|
||||||
|
type UpdateScheduleResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete schedule response structure
|
||||||
|
type DeleteScheduleResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Participant
|
||||||
|
type Participant struct {
|
||||||
|
Participant string `json:"participant,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotationInfo defines the structure for each rotation definition
|
||||||
|
type RotationInfo struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
StartDate string `json:"startDate,omitempty"`
|
||||||
|
EndDate string `json:"endDate,omitempty"`
|
||||||
|
RotationType string `json:"rotationType,omitempty"`
|
||||||
|
Participants []Participant `json:"participants,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
RotationLength int `json:"rotationLength,omitempty"`
|
||||||
|
Restrictions []Restriction `json:"restrictions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get schedule structure
|
||||||
|
type GetScheduleResponse struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Team string `json:"team,omitempty"`
|
||||||
|
Rules []RotationInfo `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List schedule response structure
|
||||||
|
type ListSchedulesResponse struct {
|
||||||
|
Schedules []GetScheduleResponse `json:"schedules,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package team provides requests and response structures to achieve Team API actions.
|
||||||
|
package team
|
||||||
|
|
||||||
|
// Member defines the structure for each team members definition
|
||||||
|
type Member struct {
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTeamRequest provides necessary parameter structure for creating team
|
||||||
|
type CreateTeamRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Members []Member `json:"members,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTeamRequest provides necessary parameter structure for updating a team
|
||||||
|
type UpdateTeamRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Members []Member `json:"members,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTeamRequest provides necessary parameter structure for deleting a team
|
||||||
|
type DeleteTeamRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamRequest provides necessary parameter structure for requesting team information
|
||||||
|
type GetTeamRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTeamsRequest provides necessary parameter structure for listing teams
|
||||||
|
type ListTeamsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTeamLogsRequest provides necessary parameter structure for listing team logs
|
||||||
|
type ListTeamLogsRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Name string `url:"name,omitempty"`
|
||||||
|
Limit int `url:"limit,omitempty"`
|
||||||
|
Order string `url:"order,omitempty"`
|
||||||
|
LastKey string `url:"lastkey,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package team
|
||||||
|
|
||||||
|
// Create team response structure
|
||||||
|
type CreateTeamResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update team response structure
|
||||||
|
type UpdateTeamResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete team response structure
|
||||||
|
type DeleteTeamResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get team response structure
|
||||||
|
type GetTeamResponse struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Members []Member `json:"members,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// List teams response structure
|
||||||
|
type ListTeamsResponse struct {
|
||||||
|
Teams []GetTeamResponse `json:"teams,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single team log entry
|
||||||
|
type TeamLogEntry struct {
|
||||||
|
Log string `json:"log"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
CreatedAt uint `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//List team logs response structure
|
||||||
|
type ListTeamLogsResponse struct {
|
||||||
|
LastKey string `json:"lastKey,omitempty"`
|
||||||
|
Logs []TeamLogEntry `json:logs,omitempty`
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016. All rights reserved.
|
||||||
|
Use of this source code is governed by a Apache Software
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Package user provides requests and response structures to achieve User API actions.
|
||||||
|
package user
|
||||||
|
|
||||||
|
// CreateUserRequest provides necessary parameter structure for creating User
|
||||||
|
type CreateUserRequest struct {
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Fullname string `json:"fullname,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
Locale string `json:"locale,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRequest provides necessary parameter structure for updating an User
|
||||||
|
type UpdateUserRequest struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
APIKey string `json:"apiKey,omitempty"`
|
||||||
|
Fullname string `json:"fullname,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
Locale string `json:"locale,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserRequest provides necessary parameter structure for deleting an User
|
||||||
|
type DeleteUserRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Username string `url:"username,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRequest provides necessary parameter structure for requesting User information
|
||||||
|
type GetUserRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
Id string `url:"id,omitempty"`
|
||||||
|
Username string `url:"username,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserRequest provides necessary parameter structure for listing Users
|
||||||
|
type ListUsersRequest struct {
|
||||||
|
APIKey string `url:"apiKey,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
// Create user response structure
|
||||||
|
type CreateUserResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user response structure
|
||||||
|
type UpdateUserResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user response structure
|
||||||
|
type DeleteUserResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Participant
|
||||||
|
type Contact struct {
|
||||||
|
To string `json:"to,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user structure
|
||||||
|
type GetUserResponse struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Fullname string `json:"fullname,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
Locale string `json:"locale,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
Escalations []string `json:"escalations,omitempty"`
|
||||||
|
Schedules []string `json:"schedules,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
Groups []string `json:"groups,omitempty"`
|
||||||
|
Contacts []Contact `json:"contacts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List user response structure
|
||||||
|
type ListUsersResponse struct {
|
||||||
|
Users []GetUserResponse `json:"users,omitempty"`
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -41,6 +41,7 @@ body.layout-mysql,
|
||||||
body.layout-newrelic,
|
body.layout-newrelic,
|
||||||
body.layout-nomad,
|
body.layout-nomad,
|
||||||
body.layout-openstack,
|
body.layout-openstack,
|
||||||
|
body.layout-opsgenie,
|
||||||
body.layout-packet,
|
body.layout-packet,
|
||||||
body.layout-pagerduty,
|
body.layout-pagerduty,
|
||||||
body.layout-postgresql,
|
body.layout-postgresql,
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
layout: "opsgenie"
|
||||||
|
page_title: "OpsGenie: opsgenie_user"
|
||||||
|
sidebar_current: "docs-opsgenie-datasource-user"
|
||||||
|
description: |-
|
||||||
|
Gets information about a specific user within OpsGenie
|
||||||
|
---
|
||||||
|
|
||||||
|
# opsgenie\_user
|
||||||
|
|
||||||
|
Use this data source to get information about a specific user within OpsGenie.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
data "opsgenie_user" "me" {
|
||||||
|
username = "me@cookie-monster.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
TODO: example
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `username` - (Required) The username (email) to use to find a user in OpsGenie.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
* `full_name` - The full name of the found user.
|
||||||
|
* `role` - The role of the found user.
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
layout: "opsgenie"
|
||||||
|
page_title: "Provider: OpsGenie"
|
||||||
|
sidebar_current: "docs-opsgenie-index"
|
||||||
|
description: |-
|
||||||
|
The OpsGenie provider is used to interact with the many resources supported by OpsGenie. The provider needs to be configured with the proper credentials before it can be used.
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpsGenie Provider
|
||||||
|
|
||||||
|
The OpenStack provider is used to interact with the
|
||||||
|
many resources supported by OpsGenie. The provider needs to be configured
|
||||||
|
with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the OpenStack Provider
|
||||||
|
provider "opsgenie" {
|
||||||
|
api_key = "key"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a user
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `api_key` - (Required) The API Key for the OpsGenie Integration. If omitted, the
|
||||||
|
`OPSGENIE_API_KEY` environment variable is used.
|
||||||
|
|
||||||
|
You can generate an API Key within OpsGenie by creating a new API Integration with Read/Write permissions.
|
||||||
|
|
||||||
|
## Testing and Development
|
||||||
|
|
||||||
|
In order to run the Acceptance Tests for development, the following environment
|
||||||
|
variables must also be set:
|
||||||
|
|
||||||
|
* `OPSGENIE_API_KEY` - The API Key used for the OpsGenie Integration.
|
|
@ -0,0 +1,67 @@
|
||||||
|
---
|
||||||
|
layout: "opsgenie"
|
||||||
|
page_title: "OpsGenie: opsgenie_team"
|
||||||
|
sidebar_current: "docs-opsgenie-resource-team"
|
||||||
|
description: |-
|
||||||
|
Manages a Team within OpsGenie.
|
||||||
|
---
|
||||||
|
|
||||||
|
# opsgenie\_team
|
||||||
|
|
||||||
|
Manages a Team within OpsGenie.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "opsgenie_user" "first" {
|
||||||
|
username = "user@domain.com"
|
||||||
|
full_name = "Cookie Monster"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
resource "opsgenie_user" "second" {
|
||||||
|
username = "eggman@dr-robotnik.com"
|
||||||
|
full_name = "Dr Ivo Eggman Robotnik"
|
||||||
|
role = "User"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "opsgenie_team" "test" {
|
||||||
|
name = "example"
|
||||||
|
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.first.username}"
|
||||||
|
role = "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
member {
|
||||||
|
username = "${opsgenie_user.second.username}"
|
||||||
|
role = "user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The name associated with this team. OpsGenie defines that this must not be longer than 100 characters.
|
||||||
|
|
||||||
|
* `member` - (Optional) A Member block as documented below.
|
||||||
|
|
||||||
|
`member` supports the following:
|
||||||
|
|
||||||
|
* `username` - (Required) The username for the member to add to this Team.
|
||||||
|
* `role` - (Required) The role for the user within the Team - can be either 'Admin' or 'User'.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the OpsGenie User.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Users can be imported using the `id`, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform import opsgenie_team.team1 812be1a1-32c8-4666-a7fb-03ecc385106c
|
||||||
|
```
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
layout: "opsgenie"
|
||||||
|
page_title: "OpsGenie: opsgenie_user"
|
||||||
|
sidebar_current: "docs-opsgenie-resource-user"
|
||||||
|
description: |-
|
||||||
|
Manages a User within OpsGenie.
|
||||||
|
---
|
||||||
|
|
||||||
|
# opsgenie\_user
|
||||||
|
|
||||||
|
Manages a User within OpsGenie.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "opsgenie_user" "test" {
|
||||||
|
username = "user@domain.com"
|
||||||
|
full_name = "Cookie Monster"
|
||||||
|
role = "User"
|
||||||
|
locale = "en_US"
|
||||||
|
timezone = "America/New_York"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `username` - (Required) The email address associated with this user. OpsGenie defines that this must not be longer than 100 characters.
|
||||||
|
|
||||||
|
* `full_name` - (Required) The Full Name of the User.
|
||||||
|
|
||||||
|
* `role` - (Required) The Role assigned to the User. Either a built-in such as 'Owner', 'Admin' or 'User' - or the name of a custom role.
|
||||||
|
|
||||||
|
* `locale` - (Optional) Location information for the user. Please look at [Supported Locale Ids](https://www.opsgenie.com/docs/miscellaneous/supported-locales) for available locales - Defaults to "en_US".
|
||||||
|
|
||||||
|
* `timezone` - (Optional) Timezone information of the user. Please look at [Supported Timezone Ids](https://www.opsgenie.com/docs/miscellaneous/supported-timezone-ids) for available timezones - Defaults to "America/New_York".
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the OpsGenie User.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Users can be imported using the `id`, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform import opsgenie_user.user da4faf16-5546-41e4-8330-4d0002b74048
|
||||||
|
```
|
|
@ -310,6 +310,10 @@
|
||||||
<a href="/docs/providers/openstack/index.html">OpenStack</a>
|
<a href="/docs/providers/openstack/index.html">OpenStack</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-opsgenie") %>>
|
||||||
|
<a href="/docs/providers/opsgenie/index.html">OpsGenie</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-packet") %>>
|
<li<%= sidebar_current("docs-providers-packet") %>>
|
||||||
<a href="/docs/providers/packet/index.html">Packet</a>
|
<a href="/docs/providers/packet/index.html">Packet</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -321,10 +325,10 @@
|
||||||
<li<%= sidebar_current("docs-providers-postgresql") %>>
|
<li<%= sidebar_current("docs-providers-postgresql") %>>
|
||||||
<a href="/docs/providers/postgresql/index.html">PostgreSQL</a>
|
<a href="/docs/providers/postgresql/index.html">PostgreSQL</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-powerdns") %>>
|
<li<%= sidebar_current("docs-providers-powerdns") %>>
|
||||||
<a href="/docs/providers/powerdns/index.html">PowerDNS</a>
|
<a href="/docs/providers/powerdns/index.html">PowerDNS</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-rabbitmq") %>>
|
<li<%= sidebar_current("docs-providers-rabbitmq") %>>
|
||||||
<a href="/docs/providers/rabbitmq/index.html">RabbitMQ</a>
|
<a href="/docs/providers/rabbitmq/index.html">RabbitMQ</a>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue