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).
City identifiers can be found in the [city list][]. Alternately you can
[search][] by name; the `city_id` can be found as the last digits of the URL:
https://openweathermap.org/city/2643743
City identifiers can be found in the [city list][]. Alternately you
can [search][] by name; the `city_id` can be found as the last digits
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
@ -18,6 +20,12 @@ https://openweathermap.org/city/2643743
## City ID's to collect weather data from.
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".
fetch = ["weather", "forecast"]
@ -42,6 +50,8 @@ https://openweathermap.org/city/2643743
- tags:
- city_id
- forecast
- condition_id
- condition_main
- fields:
- cloudiness (int, percent)
- humidity (int, percent)
@ -53,16 +63,20 @@ https://openweathermap.org/city/2643743
- visibility (int, meters, not available on forecast data)
- wind_degrees (float, wind direction in degrees)
- wind_speed (float, wind speed in meters/sec or miles/sec)
- condition_description (string, localized long description)
- condition_icon
### 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,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,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=* 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,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,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
[city list]: http://bulk.openweathermap.org/sample/city.list.json.gz
[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.
owmRequestSeveralCityId int = 20
defaultBaseURL = "https://api.openweathermap.org/"
defaultBaseUrl = "https://api.openweathermap.org/"
defaultResponseTimeout time.Duration = time.Second * 5
defaultUnits string = "metric"
defaultLang string = "en"
)
type OpenWeatherMap struct {
AppId string `toml:"app_id"`
CityId []string `toml:"city_id"`
Lang string `toml:"lang"`
Fetch []string `toml:"fetch"`
BaseUrl string `toml:"base_url"`
ResponseTimeout internal.Duration `toml:"response_timeout"`
Units string `toml:"units"`
client *http.Client
baseUrl *url.URL
}
var sampleConfig = `
@ -46,6 +49,12 @@ var sampleConfig = `
## City ID's to collect weather data from.
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".
fetch = ["weather", "forecast"]
@ -76,41 +85,10 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
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 {
if fetch == "forecast" {
var u *url.URL
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))
if err != nil {
acc.AddError(fmt.Errorf("unable to parse address '%s': %s", u, err))
continue
}
addr := base.ResolveReference(u).String()
addr := n.formatURL("/data/2.5/forecast", city)
wg.Add(1)
go func() {
defer wg.Done()
@ -126,7 +104,6 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
} else if fetch == "weather" {
j := 0
for j < len(n.CityId) {
var u *url.URL
strs = make([]string, 0)
for i := 0; j < len(n.CityId) && i < owmRequestSeveralCityId; i++ {
strs = append(strs, n.CityId[j])
@ -134,13 +111,7 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
}
cities := strings.Join(strs, ",")
u, err = url.Parse(fmt.Sprintf("/data/2.5/group?id=%s&APPID=%s&units=%s", cities, n.AppId, units))
if err != nil {
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
continue
}
addr := base.ResolveReference(u).String()
addr := n.formatURL("/data/2.5/group", cities)
wg.Add(1)
go func() {
defer wg.Done()
@ -226,6 +197,12 @@ type WeatherEntry struct {
Lon float64 `json:"lon"`
} `json:"coord"`
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 {
@ -253,9 +230,8 @@ func gatherWeatherUrl(r io.Reader) (*Status, error) {
func gatherWeather(acc telegraf.Accumulator, status *Status) {
for _, e := range status.List {
tm := time.Unix(e.Dt, 0)
acc.AddFields(
"weather",
map[string]interface{}{
fields := map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
@ -266,14 +242,22 @@ func gatherWeather(acc telegraf.Accumulator, status *Status) {
"visibility": e.Visibility,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
},
map[string]string{
}
tags := map[string]string{
"city": e.Name,
"city_id": strconv.FormatInt(e.Id, 10),
"country": e.Sys.Country,
"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 {
tm := time.Unix(e.Dt, 0)
tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3)
acc.AddFields(
"weather",
map[string]interface{}{
fields := map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
@ -297,9 +278,15 @@ func gatherForecast(acc telegraf.Accumulator, status *Status) {
"temperature": e.Main.Temp,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
},
tags,
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
}
tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3)
acc.AddFields("weather", fields, tags, tm)
}
}
@ -310,8 +297,59 @@ func init() {
}
return &OpenWeatherMap{
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"},
Units: "metric",
}
n.Init()
var acc testutil.Accumulator
@ -297,6 +298,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"forecast": "3h",
"city": "Paris",
"country": "FR",
"condition_id": "500",
"condition_main": "Rain",
},
map[string]interface{}{
"cloudiness": int64(88),
@ -306,6 +309,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"rain": 0.035,
"wind_degrees": 228.501,
"wind_speed": 3.76,
"condition_description": "light rain",
"condition_icon": "10n",
},
time.Unix(1543622400, 0),
),
@ -316,6 +321,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"forecast": "6h",
"city": "Paris",
"country": "FR",
"condition_id": "500",
"condition_main": "Rain",
},
map[string]interface{}{
"cloudiness": int64(92),
@ -325,6 +332,8 @@ func TestForecastGeneratesMetrics(t *testing.T) {
"rain": 0.049999999999997,
"wind_degrees": 335.005,
"wind_speed": 2.66,
"condition_description": "light rain",
"condition_icon": "10n",
},
time.Unix(1544043600, 0),
),
@ -358,6 +367,7 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
Fetch: []string{"weather"},
Units: "metric",
}
n.Init()
var acc testutil.Accumulator
@ -372,6 +382,8 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*",
"city": "Paris",
"country": "FR",
"condition_id": "300",
"condition_main": "Drizzle",
},
map[string]interface{}{
"cloudiness": int64(0),
@ -384,6 +396,8 @@ func TestWeatherGeneratesMetrics(t *testing.T) {
"wind_degrees": 290.0,
"wind_speed": 8.7,
"visibility": 10000,
"condition_description": "light intensity drizzle",
"condition_icon": "09d",
},
time.Unix(1544194800, 0),
),
@ -414,6 +428,7 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
Fetch: []string{"weather"},
Units: "metric",
}
n.Init()
var acc testutil.Accumulator
@ -428,6 +443,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*",
"city": "Moscow",
"country": "RU",
"condition_id": "802",
"condition_main": "Clouds",
},
map[string]interface{}{
"cloudiness": 40,
@ -440,6 +457,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556416455000000000),
"sunset": int64(1556470779000000000),
"visibility": 10000,
"condition_description": "scattered clouds",
"condition_icon": "03d",
},
time.Unix(1556444155, 0),
),
@ -450,6 +469,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*",
"city": "Kiev",
"country": "UA",
"condition_id": "520",
"condition_main": "Rain",
},
map[string]interface{}{
"cloudiness": 0,
@ -462,6 +483,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556419155000000000),
"sunset": int64(1556471486000000000),
"visibility": 10000,
"condition_description": "light intensity shower rain",
"condition_icon": "09d",
},
time.Unix(1556444155, 0),
),
@ -472,6 +495,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"forecast": "*",
"city": "London",
"country": "GB",
"condition_id": "803",
"condition_main": "Clouds",
},
map[string]interface{}{
"cloudiness": 75,
@ -484,6 +509,8 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
"sunrise": int64(1556426319000000000),
"sunset": int64(1556479032000000000),
"visibility": 10000,
"condition_description": "broken clouds",
"condition_icon": "04d",
},
time.Unix(1556444155, 0),
),
@ -492,3 +519,31 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) {
expected, acc.GetTelegrafMetrics(),
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)
}