Add lang parameter to OpenWeathermap input plugin (#6504)

This commit is contained in:
reimda 2019-10-18 13:48:23 -06:00 committed by GitHub
parent c8f4215ac5
commit 00962783f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 269 additions and 162 deletions

View File

@ -4,9 +4,11 @@ Collect current weather and forecast data from OpenWeatherMap.
To use this plugin you will need an [api key][] (app_id). To use this plugin you will need an [api key][] (app_id).
City identifiers can be found in the [city list][]. Alternately you can City identifiers can be found in the [city list][]. Alternately you
[search][] by name; the `city_id` can be found as the last digits of the URL: can [search][] by name; the `city_id` can be found as the last digits
https://openweathermap.org/city/2643743 of the URL: https://openweathermap.org/city/2643743. Language
identifiers can be found in the [lang list][]. Documentation for
condition ID, icon, and main is at [weather conditions][].
### Configuration ### Configuration
@ -18,6 +20,12 @@ https://openweathermap.org/city/2643743
## City ID's to collect weather data from. ## City ID's to collect weather data from.
city_id = ["5391959"] city_id = ["5391959"]
## Language of the description field. Can be one of "ar", "bg",
## "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl", "hr", "hu",
## "it", "ja", "kr", "la", "lt", "mk", "nl", "pl", "pt", "ro", "ru",
## "se", "sk", "sl", "es", "tr", "ua", "vi", "zh_cn", "zh_tw"
# lang = "en"
## APIs to fetch; can contain "weather" or "forecast". ## APIs to fetch; can contain "weather" or "forecast".
fetch = ["weather", "forecast"] fetch = ["weather", "forecast"]
@ -42,6 +50,8 @@ https://openweathermap.org/city/2643743
- tags: - tags:
- city_id - city_id
- forecast - forecast
- condition_id
- condition_main
- fields: - fields:
- cloudiness (int, percent) - cloudiness (int, percent)
- humidity (int, percent) - humidity (int, percent)
@ -53,16 +63,20 @@ https://openweathermap.org/city/2643743
- visibility (int, meters, not available on forecast data) - visibility (int, meters, not available on forecast data)
- wind_degrees (float, wind direction in degrees) - wind_degrees (float, wind direction in degrees)
- wind_speed (float, wind speed in meters/sec or miles/sec) - wind_speed (float, wind speed in meters/sec or miles/sec)
- condition_description (string, localized long description)
- condition_icon
### Example Output ### Example Output
``` ```
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=* cloudiness=40i,humidity=72i,pressure=1013,rain=0,sunrise=1559220629000000000i,sunset=1559273058000000000i,temperature=13.31,visibility=16093i,wind_degrees=280,wind_speed=4.6 1559268695000000000 > weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=* cloudiness=1i,condition_description="clear sky",condition_icon="01d",humidity=35i,pressure=1012,rain=0,sunrise=1570630329000000000i,sunset=1570671689000000000i,temperature=21.52,visibility=16093i,wind_degrees=280,wind_speed=5.7 1570659256000000000
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=3h cloudiness=0i,humidity=86i,pressure=1012.03,rain=0,temperature=10.69,wind_degrees=222.855,wind_speed=2.76 1559271600000000000 > weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=3h cloudiness=0i,condition_description="clear sky",condition_icon="01n",humidity=41i,pressure=1010,rain=0,temperature=22.34,wind_degrees=249.393,wind_speed=2.085 1570665600000000000
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=6h cloudiness=11i,humidity=93i,pressure=1012.79,rain=0,temperature=9.34,wind_degrees=212.685,wind_speed=1.85 1559282400000000000 > weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=6h cloudiness=0i,condition_description="clear sky",condition_icon="01n",humidity=50i,pressure=1012,rain=0,temperature=17.09,wind_degrees=310.754,wind_speed=3.009 1570676400000000000
``` ```
[api key]: https://openweathermap.org/appid [api key]: https://openweathermap.org/appid
[city list]: http://bulk.openweathermap.org/sample/city.list.json.gz [city list]: http://bulk.openweathermap.org/sample/city.list.json.gz
[search]: https://openweathermap.org/find [search]: https://openweathermap.org/find
[lang list]: https://openweathermap.org/current#multi
[weather conditions]: https://openweathermap.org/weather-conditions

View File

@ -23,20 +23,23 @@ const (
// The limit of locations is 20. // The limit of locations is 20.
owmRequestSeveralCityId int = 20 owmRequestSeveralCityId int = 20
defaultBaseURL = "https://api.openweathermap.org/" defaultBaseUrl = "https://api.openweathermap.org/"
defaultResponseTimeout time.Duration = time.Second * 5 defaultResponseTimeout time.Duration = time.Second * 5
defaultUnits string = "metric" defaultUnits string = "metric"
defaultLang string = "en"
) )
type OpenWeatherMap struct { type OpenWeatherMap struct {
AppId string `toml:"app_id"` AppId string `toml:"app_id"`
CityId []string `toml:"city_id"` CityId []string `toml:"city_id"`
Lang string `toml:"lang"`
Fetch []string `toml:"fetch"` Fetch []string `toml:"fetch"`
BaseUrl string `toml:"base_url"` BaseUrl string `toml:"base_url"`
ResponseTimeout internal.Duration `toml:"response_timeout"` ResponseTimeout internal.Duration `toml:"response_timeout"`
Units string `toml:"units"` Units string `toml:"units"`
client *http.Client client *http.Client
baseUrl *url.URL
} }
var sampleConfig = ` var sampleConfig = `
@ -46,6 +49,12 @@ var sampleConfig = `
## City ID's to collect weather data from. ## City ID's to collect weather data from.
city_id = ["5391959"] city_id = ["5391959"]
## Language of the description field. Can be one of "ar", "bg",
## "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl", "hr", "hu",
## "it", "ja", "kr", "la", "lt", "mk", "nl", "pl", "pt", "ro", "ru",
## "se", "sk", "sl", "es", "tr", "ua", "vi", "zh_cn", "zh_tw"
# lang = "en"
## APIs to fetch; can contain "weather" or "forecast". ## APIs to fetch; can contain "weather" or "forecast".
fetch = ["weather", "forecast"] fetch = ["weather", "forecast"]
@ -76,41 +85,10 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup var wg sync.WaitGroup
var strs []string var strs []string
base, err := url.Parse(n.BaseUrl)
if err != nil {
return err
}
// Create an HTTP client that is re-used for each
// collection interval
if n.client == nil {
client, err := n.createHttpClient()
if err != nil {
return err
}
n.client = client
}
units := n.Units
switch n.Units {
case "imperial", "standard":
break
default:
units = defaultUnits
}
for _, fetch := range n.Fetch { for _, fetch := range n.Fetch {
if fetch == "forecast" { if fetch == "forecast" {
var u *url.URL
for _, city := range n.CityId { for _, city := range n.CityId {
u, err = url.Parse(fmt.Sprintf("/data/2.5/forecast?id=%s&APPID=%s&units=%s", city, n.AppId, units)) addr := n.formatURL("/data/2.5/forecast", city)
if err != nil {
acc.AddError(fmt.Errorf("unable to parse address '%s': %s", u, err))
continue
}
addr := base.ResolveReference(u).String()
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -126,7 +104,6 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
} else if fetch == "weather" { } else if fetch == "weather" {
j := 0 j := 0
for j < len(n.CityId) { for j < len(n.CityId) {
var u *url.URL
strs = make([]string, 0) strs = make([]string, 0)
for i := 0; j < len(n.CityId) && i < owmRequestSeveralCityId; i++ { for i := 0; j < len(n.CityId) && i < owmRequestSeveralCityId; i++ {
strs = append(strs, n.CityId[j]) strs = append(strs, n.CityId[j])
@ -134,13 +111,7 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
} }
cities := strings.Join(strs, ",") cities := strings.Join(strs, ",")
u, err = url.Parse(fmt.Sprintf("/data/2.5/group?id=%s&APPID=%s&units=%s", cities, n.AppId, units)) addr := n.formatURL("/data/2.5/group", cities)
if err != nil {
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
continue
}
addr := base.ResolveReference(u).String()
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -226,6 +197,12 @@ type WeatherEntry struct {
Lon float64 `json:"lon"` Lon float64 `json:"lon"`
} `json:"coord"` } `json:"coord"`
Visibility int64 `json:"visibility"` Visibility int64 `json:"visibility"`
Weather []struct {
ID int64 `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
Icon string `json:"icon"`
} `json:"weather"`
} }
type Status struct { type Status struct {
@ -253,9 +230,8 @@ func gatherWeatherUrl(r io.Reader) (*Status, error) {
func gatherWeather(acc telegraf.Accumulator, status *Status) { func gatherWeather(acc telegraf.Accumulator, status *Status) {
for _, e := range status.List { for _, e := range status.List {
tm := time.Unix(e.Dt, 0) tm := time.Unix(e.Dt, 0)
acc.AddFields(
"weather", fields := map[string]interface{}{
map[string]interface{}{
"cloudiness": e.Clouds.All, "cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity, "humidity": e.Main.Humidity,
"pressure": e.Main.Pressure, "pressure": e.Main.Pressure,
@ -266,14 +242,22 @@ func gatherWeather(acc telegraf.Accumulator, status *Status) {
"visibility": e.Visibility, "visibility": e.Visibility,
"wind_degrees": e.Wind.Deg, "wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed, "wind_speed": e.Wind.Speed,
}, }
map[string]string{ tags := map[string]string{
"city": e.Name, "city": e.Name,
"city_id": strconv.FormatInt(e.Id, 10), "city_id": strconv.FormatInt(e.Id, 10),
"country": e.Sys.Country, "country": e.Sys.Country,
"forecast": "*", "forecast": "*",
}, }
tm)
if len(e.Weather) > 0 {
fields["condition_description"] = e.Weather[0].Description
fields["condition_icon"] = e.Weather[0].Icon
tags["condition_id"] = strconv.FormatInt(e.Weather[0].ID, 10)
tags["condition_main"] = e.Weather[0].Main
}
acc.AddFields("weather", fields, tags, tm)
} }
} }
@ -286,10 +270,7 @@ func gatherForecast(acc telegraf.Accumulator, status *Status) {
} }
for i, e := range status.List { for i, e := range status.List {
tm := time.Unix(e.Dt, 0) tm := time.Unix(e.Dt, 0)
tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3) fields := map[string]interface{}{
acc.AddFields(
"weather",
map[string]interface{}{
"cloudiness": e.Clouds.All, "cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity, "humidity": e.Main.Humidity,
"pressure": e.Main.Pressure, "pressure": e.Main.Pressure,
@ -297,9 +278,15 @@ func gatherForecast(acc telegraf.Accumulator, status *Status) {
"temperature": e.Main.Temp, "temperature": e.Main.Temp,
"wind_degrees": e.Wind.Deg, "wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed, "wind_speed": e.Wind.Speed,
}, }
tags, if len(e.Weather) > 0 {
tm) fields["condition_description"] = e.Weather[0].Description
fields["condition_icon"] = e.Weather[0].Icon
tags["condition_id"] = strconv.FormatInt(e.Weather[0].ID, 10)
tags["condition_main"] = e.Weather[0].Main
}
tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3)
acc.AddFields("weather", fields, tags, tm)
} }
} }
@ -310,8 +297,59 @@ func init() {
} }
return &OpenWeatherMap{ return &OpenWeatherMap{
ResponseTimeout: tmout, ResponseTimeout: tmout,
Units: defaultUnits, BaseUrl: defaultBaseUrl,
BaseUrl: defaultBaseURL,
} }
}) })
} }
func (n *OpenWeatherMap) Init() error {
var err error
n.baseUrl, err = url.Parse(n.BaseUrl)
if err != nil {
return err
}
// Create an HTTP client that is re-used for each
// collection interval
n.client, err = n.createHttpClient()
if err != nil {
return err
}
switch n.Units {
case "imperial", "standard", "metric":
case "":
n.Units = defaultUnits
default:
return fmt.Errorf("unknown units: %s", n.Units)
}
switch n.Lang {
case "ar", "bg", "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl",
"hr", "hu", "it", "ja", "kr", "la", "lt", "mk", "nl", "pl",
"pt", "ro", "ru", "se", "sk", "sl", "es", "tr", "ua", "vi",
"zh_cn", "zh_tw":
case "":
n.Lang = defaultLang
default:
return fmt.Errorf("unknown language: %s", n.Lang)
}
return nil
}
func (n *OpenWeatherMap) formatURL(path string, city string) string {
v := url.Values{
"id": []string{city},
"APPID": []string{n.AppId},
"lang": []string{n.Lang},
"units": []string{n.Units},
}
relative := &url.URL{
Path: path,
RawQuery: v.Encode(),
}
return n.baseUrl.ResolveReference(relative).String()
}

View File

@ -283,6 +283,7 @@ func TestForecastGeneratesMetrics(t *testing.T) {
Fetch: []string{"weather", "forecast"}, Fetch: []string{"weather", "forecast"},
Units: "metric", Units: "metric",
} }
n.Init()
var acc testutil.Accumulator var acc testutil.Accumulator
@ -297,6 +298,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"forecast": "3h", "forecast": "3h",
"city": "Paris", "city": "Paris",
"country": "FR", "country": "FR",
"condition_id": "500",
"condition_main": "Rain",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": int64(88), "cloudiness": int64(88),
@ -306,6 +309,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"rain": 0.035, "rain": 0.035,
"wind_degrees": 228.501, "wind_degrees": 228.501,
"wind_speed": 3.76, "wind_speed": 3.76,
"condition_description": "light rain",
"condition_icon": "10n",
}, },
time.Unix(1543622400, 0), time.Unix(1543622400, 0),
), ),
@ -316,6 +321,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"forecast": "6h", "forecast": "6h",
"city": "Paris", "city": "Paris",
"country": "FR", "country": "FR",
"condition_id": "500",
"condition_main": "Rain",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": int64(92), "cloudiness": int64(92),
@ -325,6 +332,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"rain": 0.049999999999997, "rain": 0.049999999999997,
"wind_degrees": 335.005, "wind_degrees": 335.005,
"wind_speed": 2.66, "wind_speed": 2.66,
"condition_description": "light rain",
"condition_icon": "10n",
}, },
time.Unix(1544043600, 0), time.Unix(1544043600, 0),
), ),
@ -358,6 +367,7 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
Fetch: []string{"weather"}, Fetch: []string{"weather"},
Units: "metric", Units: "metric",
} }
n.Init()
var acc testutil.Accumulator var acc testutil.Accumulator
@ -372,6 +382,8 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*", "forecast": "*",
"city": "Paris", "city": "Paris",
"country": "FR", "country": "FR",
"condition_id": "300",
"condition_main": "Drizzle",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": int64(0), "cloudiness": int64(0),
@ -384,6 +396,8 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
"wind_degrees": 290.0, "wind_degrees": 290.0,
"wind_speed": 8.7, "wind_speed": 8.7,
"visibility": 10000, "visibility": 10000,
"condition_description": "light intensity drizzle",
"condition_icon": "09d",
}, },
time.Unix(1544194800, 0), time.Unix(1544194800, 0),
), ),
@ -414,6 +428,7 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
Fetch: []string{"weather"}, Fetch: []string{"weather"},
Units: "metric", Units: "metric",
} }
n.Init()
var acc testutil.Accumulator var acc testutil.Accumulator
@ -428,6 +443,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*", "forecast": "*",
"city": "Moscow", "city": "Moscow",
"country": "RU", "country": "RU",
"condition_id": "802",
"condition_main": "Clouds",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": 40, "cloudiness": 40,
@ -440,6 +457,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556416455000000000), "sunrise": int64(1556416455000000000),
"sunset": int64(1556470779000000000), "sunset": int64(1556470779000000000),
"visibility": 10000, "visibility": 10000,
"condition_description": "scattered clouds",
"condition_icon": "03d",
}, },
time.Unix(1556444155, 0), time.Unix(1556444155, 0),
), ),
@ -450,6 +469,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*", "forecast": "*",
"city": "Kiev", "city": "Kiev",
"country": "UA", "country": "UA",
"condition_id": "520",
"condition_main": "Rain",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": 0, "cloudiness": 0,
@ -462,6 +483,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556419155000000000), "sunrise": int64(1556419155000000000),
"sunset": int64(1556471486000000000), "sunset": int64(1556471486000000000),
"visibility": 10000, "visibility": 10000,
"condition_description": "light intensity shower rain",
"condition_icon": "09d",
}, },
time.Unix(1556444155, 0), time.Unix(1556444155, 0),
), ),
@ -472,6 +495,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*", "forecast": "*",
"city": "London", "city": "London",
"country": "GB", "country": "GB",
"condition_id": "803",
"condition_main": "Clouds",
}, },
map[string]interface{}{ map[string]interface{}{
"cloudiness": 75, "cloudiness": 75,
@ -484,6 +509,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556426319000000000), "sunrise": int64(1556426319000000000),
"sunset": int64(1556479032000000000), "sunset": int64(1556479032000000000),
"visibility": 10000, "visibility": 10000,
"condition_description": "broken clouds",
"condition_icon": "04d",
}, },
time.Unix(1556444155, 0), time.Unix(1556444155, 0),
), ),
@ -492,3 +519,31 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
expected, acc.GetTelegrafMetrics(), expected, acc.GetTelegrafMetrics(),
testutil.SortMetrics()) testutil.SortMetrics())
} }
func TestFormatURL(t *testing.T) {
n := &OpenWeatherMap{
AppId: "appid",
Units: "units",
Lang: "lang",
BaseUrl: "http://foo.com",
}
n.Init()
require.Equal(t,
"http://foo.com/data/2.5/forecast?APPID=appid&id=12345&lang=lang&units=units",
n.formatURL("/data/2.5/forecast", "12345"))
}
func TestDefaultUnits(t *testing.T) {
n := &OpenWeatherMap{}
n.Init()
require.Equal(t, "metric", n.Units)
}
func TestDefaultLang(t *testing.T) {
n := &OpenWeatherMap{}
n.Init()
require.Equal(t, "en", n.Lang)
}