Log Insight is such an underrated tool. As a consultant I use it all the time, it makes it simple and fast to find issues and solutions. Frankly speaking any VMware shop not running Log Insight is letting there company down.
There is multiple ways to get insight out of Log Insight. You can use the GUI; setup webhooks for third party intergration; get it to send you an email; send alerts to vRops or use the Log Insight API. The API is split into two, an ingestion API and a query API. Today I am going to show you how to use the query API with the use of Powershell.
Using Log Insight query API with Powershell
Like so many others, I like Powershell. It is simple to understand and fairly quickly to execute your commands. Log Insight does not have a supported PowerCli module and as such you cannot rely VMware for providing you with a working solution. The Log Insight API is REST based and all the calls are being done via https calls. This means that the uri needs to be formatted in such a way, that it can be passed as a https call. You will need to understand how to structure the URI for the API call. This can be somewhat cumbersome. Having to use encoded symbols. As an example %20% is a space and all spaces in the URI needs to be replaced with %20%. So just to construct the URI, will take sometime. It would be nice to have a URI builder tool. So simple queries could be entered and translated into usable queries for Log Insight. More on how to construct URI later.
There are four variables which are all needed. IP or FQDN of your Log Insight solution. Which authentication method that is used. For local accounts the provider is “Local” and for Active Directory it is “ActiveDirectory”. Not really sure what it is for vIDM (VMware Identity Manager). Lastly username and password needs to be defined. That is all the variables for you to get started. The next section of the script is trust of certificates. This ensures that all certificates are trusted while running the script.
After SSL trust, a login information is converted to json. This is going to be used when logging into Log Insight and getting the session id. Which is what comes next. I use the invoke-restmethod and send the json payload to a Log Insight URI which then after validating login information, passes back a session id. This session id is the token for which access are given to Log Insight, until the session id expires and a new session id need to be requested.
Next a header is constructed which includes the session id form before. This header is what is passed along with the request for information. Lastly execute a query and then display the data from the query. Quite simple right.
What is not so simple is the construction of the URI. I will talk more about that after the script…
The script
Script is available on github, as can be seen here.
Queries
The last two lines of the above script, is what actually returns data. The rest is just to get you going. Now lets see what is returned back to the $Query variable.
PS > $Query StatusCode : 200 StatusDescription : Content : {"complete":true,"duration":54,"events":[{"text":"2017-07-05T16:09:49.105Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDon... RawContent : HTTP/1.1 200 ACCESS-CONTROL-EXPOSE-HEADERS: x-li-timestamp,x-li-session-id,X-LI-Build x-li-session-id: Me7cZbmByAgVR713QygJg6hnhRr4aVtMeZ05x6oq4lL/IujGDp+cqfVjYdbvTReHvc9Ss2aTONXa5s/PGN3vx/aUDcQQQ9... Forms : {} Headers : {[ACCESS-CONTROL-EXPOSE-HEADERS, x-li-timestamp,x-li-session-id,X-LI-Build], [x-li-session-id, Me7cZbmByAgVR713QygJg6hnhRr4aVtMeZ05x6oq4lL/IujGDp+cqfVjYdbvTReHvc9Ss2aTONXa5s/PGN3vx/aUDcQQQ98zo0j0IfjtolO2Zz8fHvLOIFS1ocgEEIxm/vmPbf70krHvYhTDW4yYW6AQhqKP6lP3Y00GxUP8vTWqBuO3owagtPp692WIk7M2pzUPKqW0 iJvLITXJroLdqfi3YX0mGL5L/4iACkFfcn9PDJOPZumsco8/NyWx//JBsINpV+tifF2IHwahfkSf5A==], [X-LI-Build, 5654101], [x-li-timestamp, 1499271325]...} Images : {} InputFields : {} Links : {} ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 50721
First thing to look at is StatusCode. A http status code of 200, means the http request was successful. Also see under Content, the complete status. If complete is set to true everything went according to plan else something went wrong. Content is also where all the data is at, more on that in a moment. RawContent is including the http header, but the rest of the content is from Content. Headers is only http header, so no content here.
The only two fields I am interested in is StatusCode and Content. Already explained StatusCode, so let me dig into Content. It might not be obvious, but Content is presented in json format.
Just calling $Query.Content will give you one long string of json. This need to be converted into something more useful in terms of Powershell structure. The trick here is to pipe the data in to the ConvertFrom-json commandlet.
PS > $Query.Content | ConvertFrom-Json complete duration events -------- -------- ------ True 54 {@{text=2017-07-05T16:09:49.105Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDone] Completed callback; timestamp=1499271324643; fields=System.Object[]}, @{text=2017-07-05T16:09:49.104Z Host1.domain.local Vpxa: verbose...
As seen this makes Powershell understand the data and it makes it much easier to work with for you and me. Data is in the events variable. Lets take a closer look. First is the properties of $Events.
PS > $Events | gm TypeName: System.Management.Automation.PSCustomObject Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() fields NoteProperty Object[] fields=System.Object[] text NoteProperty string text=2017-07-05T16:09:49.105Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDone] Completed callback timestamp NoteProperty long timestamp=1499271324643
There are three NoteProperty fields which are the once holding the data. The text field holds the syslog message them self. This is pure messages with no added data structure. The field timestamp, has the timestamp for each syslog message and last but not least. The field, fields contains the extract fields and other metadata which is not directly available in the syslog message it self.
PS > $Events = ($Query.Content | ConvertFrom-Json).Events PS > $Events text timestamp fields ---- --------- ------ 2017-07-05T16:09:49.105Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDone] Completed callback 1499271324643 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:49.104Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDone] Starting next WaitForUpdates() call to hostd 1499271324643 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:49.104Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [VpxaHalCnxHostagent::ProcessUpdate] Applying updates from 1898646 to 1898647 (at 1898646) 1499271324643 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:49.104Z Host1.domain.local Vpxa: verbose vpxa[FFFC0A60] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-69624820] [WaitForUpdatesDone] Received callback 1499271324642 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.629Z Host1.domain.local Vpxa: verbose vpxa[30681B70] [Originator@6876 sub=VpxProfiler] [1-] CheckEnvBrowserChanges (took 76 ms) 1499271324167 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.602Z Host1.domain.local Hostd: info hostd[6CE48B70] [Originator@6876 sub=Hostsvc.DvsTracker opID=f4505633 user=vpxuser] FetchUplinkDVPortgroups: added 0 items 1499271324133 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.553Z Host1.domain.local Vpxa: verbose vpxa[30681B70] [Originator@6876 sub=VpxProfiler] [1+] CheckEnvBrowserChanges 1499271324099 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.492Z Host1.domain.local Rhttpproxy: verbose rhttpproxy[FFC32B70] [Originator@6876 sub=Proxy Req 69999] Resolved endpoint : [N7Vmacore4Http16LocalServiceSpecE:0xffdc5760] _serverNamespace = /sdk _isRedirect = false _port = 8307 1499271324032 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.289Z Host3.domain.local Vpxa: verbose vpxa[6C2EEB70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-68b2611] [WaitForUpdatesDone] Completed callback 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.289Z Host3.domain.local Vpxa: verbose vpxa[6C2EEB70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-68b2611] [WaitForUpdatesDone] Starting next WaitForUpdates() call to hostd 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.288Z Host3.domain.local Vpxa: verbose vpxa[6C2EEB70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-68b2611] [VpxaHalCnxHostagent::ProcessUpdate] Applying updates from 32061811 to 32061812 (at 32061811) 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.288Z Host3.domain.local Vpxa: verbose vpxa[6C2EEB70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-68b2611] [WaitForUpdatesDone] Received callback 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.283Z Host3.domain.local Vpxa: verbose vpxa[6D856B70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-2eb7566d] [WaitForUpdatesDone] Completed callback 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.283Z Host3.domain.local Vpxa: verbose vpxa[6D856B70] [Originator@6876 sub=VpxaHalCnxHostagent opID=WFU-2eb7566d] [WaitForUpdatesDone] Starting next WaitForUpdates() call to hostd 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.283Z Host3.domain.local Vpxa: verbose vpxa[6D856B70] [Originator@6876 sub=vpxaInvtVm opID=WFU-2eb7566d] [VpxaInvtVmChangeListener] Guest DiskInfo Changed 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.283Z Host3.domain.local Vpxa: verbose vpxa[6D856B70] [Originator@6876 sub=halservices opID=WFU-2eb7566d] [VpxaHalServices] VmGuestDiskChange Event for vm(134) 251 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na... 2017-07-05T16:09:48.283Z Host3.domain.local Vpxa: verbose vpxa[6D856B70] [Originator@6876 sub=hostdvm opID=WFU-2eb7566d] [VpxaHalVmHostagent] 251: GuestInfo changed 'guest.disk' 1499271323859 {@{name=hostname; startPosition=25; length=17}, @{na...
Lets look at the fields content. Some of the data looks a bit weird, like {name=hostname; startPosition=25; length=17}. Whereas {name=source; content=10.10.10.10} is easy to understand.
PS > (($Query.Content | ConvertFrom-Json).events | select fields)[0].fields -split("@") {name=hostname; startPosition=25; length=17} {name=event_type; content=v4_a0f23ef6} {name=appname; startPosition=43; length=4} {name=data_type; content=syslog} {name=source_type; content=vSphere} {name=source; content=10.10.10.10}
StartPosition is referring to the position in the syslog message where data for the field is extracted from and length is how long the text for the field is. In this example of hostname, it is the hostname in the syslog message that is being extracted, ie. “Host3.domain.local”. The other field which contains data, is not taken directly from the syslog message, but is added as context. Such as source which is the IP/FQDN of the source of the syslog message which does not have to be the same as hostname or event_type which is part of Log Insights machine learning ability. That assigns a unique event type for each uniquely syslog message type.
Here is an exmaple of how you can use Powershell to get the data based on position of data.
PS > $data = "A lot of data in here" PS > $data.Substring(2,3)
output of $data will be “lot”. The first number in the substring parentheses is start position and after the comma is length of data wanted. So it just a matter of using startPosition and length of the field to get the correct data from the syslog message.
URI
In the script examples above I just use the simple uri of “https://$LIHost/api/v1/events/”. It means I get all log entries for a short time period, starting from the execution and going back. Or in other word, I did not define any parameters to limit my search or set a time range, meaning Log Insight just return a limited set of data from present time.
This can of course be changed. It is all in the way the uri is constructed. Like I said earlier it a bit cumbersome to do as the Log Insight API does not understand space or special characters so everything has to be formatted correctly. Luckily Powershell has a simple command which helps with the formatting of data. [uri]::EscapeUriString(), takes a string and returns it formatted. To better understand what it does here a quote from MSDN.
Use the EscapeUriString method to prepare an unescaped URI string to be a parameter to the Uri constructor.
By default, the EscapeUriString method converts all characters, except RFC 2396 unreserved characters, to their hexadecimal representation. If International Resource Identifiers (IRIs) or Internationalized Domain Name (IDN) parsing is enabled, the EscapeUriString method converts all characters, except for RFC 3986 unreserved characters, to their hexadecimal representation. All Unicode characters are converted to UTF-8 format before being escaped.
This method assumes that stringToEscape has no escape sequences in it.
By default, the string is escaped according to RFC 2396. If International Resource Identifiers (IRIs) or Internationalized Domain Name (IDN) parsing is enabled, the string is escaped according to RFC 3986 and RFC 3987. See these RFCs for a definition of reserved and unreserved characters.
Below you can see an example of using [uri]::EscapeUriString()
PS > $String = "https://$LIHost/api/v1/events/text/GuestInfo changed 'guest.disk'" PS > $LIQuery = [uri]::EscapeUriString($String) PS > $Query = Invoke-WebRequest -Uri $LIQuery -Headers $Headers PS > ($Query.Content | ConvertFrom-Json).events
I have not added this to the script as I am still contemplating wheatear to add a function/module which can handle the way queries are made in the Log Insight API and give back a probably formatted URI or not… Time will tell, but for now you have to create the query manually and then parse it as shown above before adding the uri to the script.
Conclusion
I hope this was a straight forward introduction to using the Log Insight Query API with Powershell. I know this is not a build to production module, but it will hopefully get you started with using the Log Insight Query API and then build on top of this with your own knowledge. The Log Insight API is well documented a publicly available at https://www.vmware.com/go/loginsight/api
Also Luc Dekens as create a Log Insight Module for Log Insight 3.3, if it is working I do not know, but AFAIK it not supported by VMware. You can read more about it here.
That concludes all I had to share, thank you for reading this far and please do share this with the community.
2 thoughts on “Using Log Insight query API with Powershell”
Will a query show performance of vms too?
In a multitenant environment I am try to use the datasets (collection of VMs) in log insight to display perf stats in a dashboard.
If you have performance data in Log Insight. But normally you don’t have that. I guess that depends on your use case.
A query will get you the data you queried for, so yes.